2018년 10월 22일 월요일

MOOC 강좌 리스트

[출처 : https://cafe.naver.com/ ]

MOOC 강좌 리스트를 확인하고 지금 바로 무료로 수강해 보세요!


■ KOCW : Korea Open CourseWare
  http://www.kocw.net/home/index.do


 K-MOOC : 한국형 무크
  http://www.kmooc.kr/


 스누온 : 서울대학교
  https://etl.snu.ac.kr/login.php


 KOOC : 한국과학기술원(KAIST)
  https://kaist.edwith.org/


 STAR MOOC : 과학기술 분야 온라인 공개강좌 서비스
  https://www.starmooc.kr/


 MOOC: 연세대학교 온라인 공개강좌
  http://mooc.yonsei.ac.kr/


KeduX(케이에듀엑스) : 울산과학기술원
  http://www.kedux.kr/


 U-MOOC : 울산대학교
  http://umooc.ulsan.ac.kr/usc/main/layout.do


 OCW : 서강대학교
  http://ocw.sogang.ac.kr/main/main.jsp


 스노우 : 숙명여대
  http://www.snow.or.kr/


 HOWL : 한양대
  http://www.howl.or.kr/


 OCW : 중앙대
  http://ocw.cau.ac.kr/


 OCW : 한국외국어대학교
  http://contents.hufs.ac.kr/


 OCW : 성균관대학교
  https://socw.skku.edu/


 MOOC 2.0 : 동티모르 국립대
  https://www.khmooc.org/home


 경기도 지식캠퍼스
  https://www.gseek.kr/main/intro


 경기도일자리재단
  https://www.dream.go.kr/dream/index.do


 광주광역시 시민사이버학습센터
  https://elearning.gwangju.go.kr/


 서울시 평생학습포털
  http://sll.seoul.go.kr/main/MainView.dunet


 플라톤아카데미TV
  https://www.youtube.com/user/platonacademytv/videos


 배움나라(한국정보화진흥원)
  http://www.estudy.or.kr/estudy2.0/kor/index.asp


 생활코딩
  https://opentutorials.org/course/1


 촉아카데미
  http://www.playchalk.com/main/

2018년 9월 19일 수요일

linux를 AP로 사용하기


CentOS7 에 USB Type 의 Wireless Ethernet Adapter를 사용하여, AP를 만들어 볼려고 합니다. CentOS7 이 설치되어 있는 작은 PC 와 USB Wireless NIC만 있으면 간단하게 만들 수 있습니다
사전 구성, 아래와 같이 eno1 이 internet 에 연결되어 있는 라인이며,  wlp0s26u1u3 이 내부 IP로 보면 됩니다. 여기서 주의할 점은, NetworkManager가 비활성화 되어 있어야 하며, NM_CONTROLLED가 No로 되어있어야 합니다.
# ifconfig
eno1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet xxx.xxx.xxx.5  netmask 255.255.255.0  broadcast 192.168.79.255
        inet6 fe80::a62:66ff:fe33:e930  prefixlen 64  scopeid 0x20
        ether 08:62:66:33:e9:30  txqueuelen 1000  (Ethernet)
        RX packets 20879680  bytes 19284054296 (17.9 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 23917293  bytes 29325471975 (27.3 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 20  memory 0xf7c00000-f7c20000  

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10
        loop  txqueuelen 1  (Local Loopback)
        RX packets 20  bytes 1604 (1.5 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 20  bytes 1604 (1.5 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

wlp0s26u1u3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet yyy.yyy.yyy.1  netmask 255.0.0.0  broadcast 10.255.255.255
        inet6 fe80::66e5:99ff:fefb:782e  prefixlen 64  scopeid 0x20
        ether 64:e5:99:fb:78:2e  txqueuelen 1000  (Ethernet)
        RX packets 1533756  bytes 107787902 (102.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2162101  bytes 3268888586 (3.0 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
# cat /etc/sysconfig/network-scripts/ifcfg-wlp0s26u1u3 
TYPE="Wireless"
MODE="AP"
BOOTPROTO="static"
NAME="wlp0s26u1u3"
DEVICE="wlp0s26u1u3"
ONBOOT="yes"
IPADDR="yyy.yyy.yyy.1"
NETMASK="255.0.0.0"
NM_CONTROLLED="no"

패키지 설치 및 구성 파일
# yum install hostapd iw
# cat /etc/hostapd/hostapd.conf 
#
# This will give you a minimal, insecure wireless network.
# 
# DO NOT BE SATISFIED WITH THAT!!!
#
# A complete, well commented example configuration file is
# available here:
#
# /usr/share/doc/hostapd/hostapd.conf
#
# For more information, look here:
#
# http://wireless.kernel.org/en/users/Documentation/hostapd
#

ctrl_interface=/var/run/hostapd
ctrl_interface_group=wheel

# Some usable default settings...
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0

# Uncomment these for base WPA & WPA2 support with a pre-shared key
#wpa=3
#wpa_key_mgmt=WPA-PSK
#wpa_pairwise=TKIP
#rsn_pairwise=CCMP

# DO NOT FORGET TO SET A WPA PASSPHRASE!!
#wpa_passphrase=YourPassPhrase

# Most modern wireless drivers in the kernel need driver=nl80211
driver=nl80211

# Customize these for your local configuration...
interface=wlp0s26u1u3
hw_mode=g
channel=7
ssid=[이름수정]

# 802.11n
wme_enabled=0
wmm_enabled=0
ieee80211n=1
ht_capab=[HT40+][SHORT-GI-40][DSSS_CCK-40]

# Others
ap_isolate=1
wpa=2
wpa_key_mgmt=WPA-PSK
wpa_pairwise=CCMP
#rsn_pairwise=CCMP
wpa_passphrase=[공유기비번수정]
wpa_group_rekey=1800

# Only root can configure hostapd
ctrl_interface_group=0
DHCP 데몬 구성
# yum install dhcp

# cat /etc/dhcp/dhcpd.conf 
#
# DHCP Server Configuration file.
#   see /usr/share/doc/dhcp*/dhcpd.conf.example
#   see dhcpd.conf(5) man page
#

default-lease-time 600;
max-lease-time 7200;
INTERFACES="wlp0s26u1u3";
option domain-name "";
max-lease-time 7200;
log-facility local7;

subnet yyy.yyy.yyy.0 netmask 255.0.0.0 {
    range yyy.yyy.yyy.2 yyy.yyy.yyy.20;
    option routers [yyy.yyy.yyy.1];
    option domain-name-servers [DNS Server IP];
}
IP_Forward 설정
# cat /etc/sysctl.conf 
# sysctl settings are defined through files in
# /usr/lib/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.d/.
#
# Vendors settings live in /usr/lib/sysctl.d/.
# To override a whole file, create a new file with the same in
# /etc/sysctl.d/ and put new settings there. To override
# only specific settings, add a file with a lexically later
# name in /etc/sysctl.d/ and put new settings there.
#
# For more information, see sysctl.conf(5) and sysctl.d(5).

net.ipv4.ip_forward = 1
방화벽 설정
# firewall-cmd --zone=public --add-masquerade --permanent
# firewall-cmd --list-all --zone=public
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eno1 wlp0s26u1u3
  sources: 
  services: dhcpv6-client samba ssh
  ports: 
  protocols: 
  masquerade: yes
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
시스템 데몬 설정
# systemctl enable hostapd
# systemctl enable dhcpd
설정 완료 후에 시스템 리부팅 하면 멋진 access point 가 만들어 집니다. 이제 스마트폰에서 AP검색하면 보일것이고, 미리 설정해둔 password를 사용하여 internet 에 접근이 될것입니다.

CENTOS7에서 Broadcom 무선 랜카드 드라이버 설치하기


[출처 : http://elrepo.org/tiki/wl-kmod ]


Source RPMs for Broadcom drivers

Please note: The current Broadcom closed-source Linux drivers when built according to the instructions provided here have provided functional wireless capability to some users, but have failed for other users.

Broadcom Corp. provides closed-source IEEE 802.11a/b/g/n Linux drivers for use with Broadcom's BCM4311, BCM4312, BCM4313, BCM4321, BCM4322, BCM4331, BCM4352, BCM4360, BCM43142, BCM43224, BCM43225, BCM43227 and BCM43228 wireless chipsets. We (ELRepo) cannot create and redistribute binary RPMS for these drivers due to the Broadcom licensing restrictions (i.e., liability issues). However, we do make available no-source SRPMS (.nosrc.rpm) which contain the framework required to build the binaries, but lack the proprietary code and are not subject to Broadcom's licensing restrictions.

These no-source SRPMS can be used by the end user to build kABI-compatible binary drivers for local use. These kABI-compatible binaries should be usable after future kernel updates, so there is no need to recompile them for each new kernel. Please note that the nosrc.rpm package does not build kmod-wl for kernel-xen and kmod-wl cannot be built on a system running kernel-xen.

To build a kmod-wl binary, you will need to (1) configure a build tree, (2) download the wl-kmod*nosrc.rpm for your EL release, (3) download the closed-source tarball from Broadcom for your architecture (i.e., 32-bit vs 64-bit) into the build tree and (4) initiate the kmod-wl build. For convenience, the kernel development package for the running kernel will be used in the build process, as opposed to our normal policy of building against an older kernel development package.

The kmod-wl package can be built on any system running the same EL release/architecture as the target system. If you are building on the target system and lack network connectivity, then the downloads can be made onto a USB flash drive and copied onto your system.

Below are the detailed instructions on how to build the binary packages from our no-source SRPM. If you are already familiar with using rpmbuild to build packages, then this process should only take you 5-10 minutes to complete.


Kmod-wl Usability Across EL Point Releases (or ″Why doesn′t my wireless work after the last point release update?″):


If you are only interested in building kmod-wl for the latest kernel, then you do not really need to read this section, but it would not hurt to understand the underlying issues.

The EL6 and EL7 kmod-wl binaries are typically only functional within a range of EL point releases (e.g., 6.8-6.9, 7.5) due to changes in the wireless stack-related kernel API of the EL 6 and EL 7 kernels. RHEL backports (external link) components of recent kernels so that more modern kernel features and security fixes are available to the user. These backports become most notable when a new point release becomes available.

Our kmods continue to work after updating the kernel as long as the Kernel Application Binary Interface (kABI) remains unchanged with respect to our packages. EL point releases sometime break this compatibility. For the kmod-wl package, we make changes to the wl-kmod srpm to accommodate these changes. One down side is that the newly built kmod-wl only works within the newest RHEL point release after a wl-kmod srpm update. To maintain backward compatibility of the wl-kmod srpm, we craft it to build slightly different kmod-wl packages depending on the point release kernel used to build it.

If your interest is to only use the newest kernel, then you can just boot to the newest kernel and follow the instructions (below) to build a usable kmod-wl. However, if you need to continue using an older kernel, then you can use the updated wl-kmod srpm to build a kmod-wl package that works with the older kernel.

How to do that: If you build kmod-wl from the updated srpm after booting the latest kernel, then you can save that kmod-wl for use with the latest kernel. Likewise, you can build kmod-wl from the updated srpm after booting an older point release kernel and then save that kmod-wl for use with the older point release kernel. (For users familiar with using rpmbuild, you can also use the " --define 'kversion TARGET-KVERSION ' " option to avoid booting different kernels.) Unfortunately, you can only have one of those kmod-wl packages installed at a time, so save both kmod-wl packages, but only install one of them. Note that they have exactly the same version (i.e., their rpm names are identical), so save them in separate folders.

For example, you can build kmod-wl under an EL 7.2 kernel and use it with EL 7.2 kernels, but it will not function when you boot EL 7.1 or EL 7.3 kernels. Likewise, you can build kmod-wl under an EL 7.3 kernel and use it with EL 7.3 kernels, but it will not function when you boot EL 7.1 or EL 7.2 kernels.

If you need a kmod-wl package that works within one of the following ranges, then boot a kernel in that range and build/install kmod-wl as described below.

   Supported EL 6 Point Release Ranges: (1) 6.3 - 6.4, (2) 6.5 - 6.6, (3) 6.7, (4) 6.8 - 6.10

   Supported EL 7 Point Release Ranges: (1) 7.0, (2) 7.1, (3) 7.2, (4) 7.3, (5) 7.4, (6) 7.5


Build and install kmod-wl for EL6/7

   Please note:

     If your current kmod-wl package works with an older kernel and wireless networking is your only Internet access method,
     then boot the older kernel and download the new srpm and Broadcom source before proceeding.


1) Install needed tools/packages:


   1a) EL6:

     # yum groupinstall 'Development Tools'

     # yum install redhat-lsb kernel-abi-whitelists

     # yum install kernel-devel-$(uname -r)

       EL 6.4 - 6.6 Note: Due to a bug in redhat-rpm-config-9.0.3-42.el6, a modification is required to fix the
       kabi-whitelist target in /usr/lib/rpm/redhat/find-requires.ksyms before you build kmod-wl.

       Please run as root (as a single copy/paste):

       [[ $(rpm -q redhat-rpm-config|grep 9.0.3-42|wc -l) -gt 0 ]] && sed -i \
       's@/lib/modules/kabi/kabi_whitelist@/lib/modules/kabi-current/kabi_whitelist@g' \
       /usr/lib/rpm/redhat/find-requires.ksyms

       The above modification is not needed for EL 6.7 or later.


   1b) EL7:

     # yum group install 'Development Tools'

     # yum install redhat-lsb kernel-abi-whitelists

     # yum install kernel-devel-$(uname -r)

       EL 7.0 Note: Due to a bug in redhat-rpm-config-9.1.0-63.el7, a modification is required to fix the
       kabi-whitelist target in /usr/lib/rpm/redhat/find-requires.ksyms before you build kmod-wl.

       Please run as root (as a single copy/paste):

       [[ $(rpm -q redhat-rpm-config|grep 9.1.0-63|wc -l) -gt 0 ]] && sed -i \
       's@/lib/modules/kabi/kabi_whitelist@/lib/modules/kabi-rhel70/kabi_whitelist@g' \
       /usr/lib/rpm/redhat/find-requires.ksyms

       The above modification is not needed for EL 7.1 or later.




2) As a regular user (not as root), configure a build tree and minimal .rpmmacros:


   $ mkdir -p ~/rpmbuild/{BUILD,RPMS,SPECS,SOURCES,SRPMS}

   $ echo -e "%_topdir $(echo $HOME)/rpmbuild\n%dist .el$(lsb_release -s -r|cut -d"." -f1).local" >> ~/.rpmmacros



3) Download wl-kmod*nosrc.rpm



4) Download the Broadcom driver matching your architecture (i.e., 32-bit vs 64-bit):


   from:   http://www.broadcom.com/support/802.11 (external link) (scroll down to "Linux® STA 32-bit (or 64-bit) drivers")

   to:      ~/rpmbuild/SOURCES/



5) Build kmod-wl as a regular user (not as root):


   $ rpmbuild --rebuild --define 'packager <your-name>' /<path-to-nosrc.rpm>/wl-kmod*nosrc.rpm
   ...
   Wrote: /home/<user>/rpmbuild/RPMS/x86_64/kmod-wl-<version>.x86_64.rpm
   ...
   + exit 0



6) If ndiswrapper is installed and is no longer needed, then remove it:


     # yum remove \*ndiswrapper\*



7) Install kmod-wl:


Note for EL7: Recent hardware is often shipped with an Unified Extensible Firmware Interface (UEFI) in which Secure Boot is enabled. The self-compiled kmod-wl is NOT signed for Secure Boot and the corresponding wl module will not load on an UEFI Secure Boot-enabled system. You may see an error message to the effect that "Required key not available". If you wish to continue using Secure Boot in combination with EL7/kmod-wl, then you will need to sign the kmod-wl.rpm you have created using your own private Secure Boot key and install the corresponding public key on your system as explained in Signing Kernel Modules for Secure Boot (external link). Otherwise, you can disable Secure Boot in the UEFI configuration to avoid this issue.



   # rpm -Uvh /path-to-rpm/kmod-wl*rpm

   OR

   # yum --nogpgcheck localinstall /path-to-rpm/kmod-wl*rpm

       Please note: It is recommended, but is not critical, that you uninstall any older version of kmod-wl (i.e., rpm -e kmod-wl) before installing the new kmod-wl package.



8) Reboot or to start wireless now:


   8a) EL6:

     # modprobe -r b43 b43legacy ssb wl lib80211
     # modprobe -r bcma (Note: needed for EL 6.4 and later)
     # modprobe lib80211_crypt_tkip
     # modprobe wl

   8b) EL7:

     # modprobe wl



9) Store kmod-wl*rpm for safe keeping


10) Optional - Remove the build tree:


   $ rm -rf ~/rpmbuild

2018년 8월 28일 화요일

공통 Invalid Object Compile 방법

[ 출처 : http://datacloud.tistory.com/ ]

1. Oracle DB 에서 왜 주기적인 Invalid Object ReCompile 이 필요한가 ?
  아래의 여러 가지 사유들로, Object 들이 Invalid 상태로 빠지게 됩니다.
  - Oracle DB 에 대한 Major, Minor Upgrade 수행 후
  - Function 이 사용하는 Table 의 Column 추가/삭제/변경이 발생되는 경우

  보통은 Object 들을 CALL/USE 하게 되면, 다시 Valid 상태로 자동 Compile 이 되나,
  때로는 아래의 문제들을 발생시키기도 합니다. DB 장애의 원인이 되기도 합니다.
  - ORA-4020 : Deadlock 이 감지된 상태에서의 library object 에 대한 lock을 획득 실패, <Note:130409.1> 참조
  - ORA-00911 : User miss 로 (Descriptive Flexfield 등을 신규생성/수정 시, Segment name 의 첫 CHARACTER 로 숫자나 알파벳 혹은 한글이 아닌 특수문자(ex> "[]")를 사용) Object 가 Compile 불가 상태로 변경된 경우

  따라서, DBA 들은, 주기적으로 Invalid Object 들을 조사해서, 사전에 Recompile 을 통해, 관련 Object 들의 문제를 조치하고, 예기치 못한 Issue 발생에 대비하고 있습니다.


2. Invalid Object 를 Re compile 하는 방법
  기본적으로 Oracle DB 에는 Invalid Object 들을 Compile 하는 5가지 방법이 존재합니다.
  - DBMS_DDL
  - DBMS_UTILITY
  - UTL_RECOMP
  - UTLRP.SQL
  - Manually Recompile


 저는 간단한 Script 를 이용해서, Manual recompile 하기를 좋아 하는데, text mode 의 telnet 창에 접속하여, 다수의 서버들에서 logging 하면서 동시 작업하기가 편하기 때문입니다. 각각의 방법들은 장단점들이 있지만, 여기서는 언급하지 않고, 수행 방법들만 기술하도록 하겠습니다.


2-A. DBMS_DDL.ALTER_COMPILE
  Definition : ALTER PROCEDUREFUNCTIONPACKAGE [.] COMPILE [BODY]
  Syntax : Exec dbms_ddl.alter_compile ( type , schema, name);
  - Type : Must be either PROCEDURE, FUNCTION, PACKAGE, PACKAGE BODY or TRIGGER.

  Example
  SQL> exec dbms_ddl.alter_compile ('PROCEDURE','SCOTT','TEST');
      PL/SQL procedure successfully completed.
  SQL>


2-B. DBMS_UTILITY.COMPILE_SCHEMA
  Definition : This procedure compiles all procedures, functions, packages, and triggers in the specified schema.
  Syntax : Exec dbms_utility.compile_schema ( schema,compile all)
  - Compile All : Object type ( procedure, function, packages,trigger)

  Example
  SQL> exec dbms_utility.compile_schema('SCOTT');
       PL/SQL procedure successfully completed.
  SQL>

2-C. UTL_RECOMP
  Definition : This script is particularly useful after a major-version upgrade that typically invalidates all PL/SQL and Java objects.
  Syntax : Exec UTL_RECOMP.RECOMP_SERIAL ();
  - Note: Required SYS user to run this package.

  Example
  SQL> Exec UTL_RECOMP.RECOMP_SERIAL ();
       PL/SQL procedure successfully completed.
  SQL>

2-D. UTLRP.SQL scripts

  Definition : Recompiles all existing PL/SQL modules that were previously in an INVALID state, such as packages, procedures, and types.
  Syntax : Located: $ORACLE_HOME/rdbms/admin
  - Note: Required SYS user to run this script.
  - Recommended: After upgrade or migrate database.

  Example
  SQL> @ c:\oracle\product\10.2.0\db_1\rdbms\admin\UTLRP.SQL
    TIMESTAMP
    -----------------------------------------------------------------------
    COMP_TIMESTAMP UTLRP_BGN 2012-02-03 10:40:22
    PL/SQL procedure successfully completed.
    COMP_TIMESTAMP UTLRP_END 2012-02-03 10:40:29
    PL/SQL procedure successfully completed.
  SQL>

2-E. Manually recompiling : Best Approach to Recompile all Invalid Objects
  Syntax : ALTER OBJECT_TYPE.OBJECT_NAME COMPILE ;
  - Note: VIEW, SYNONYM, PUBLIC SYNONYM, PROCEDURE, FUNCTION, PACKAGE, PACKAGE BODY, TRIGGER, UNDEFINED (MATERIALIZED VIEW), JAVA CLASS, TYPE, TYPE BODY

  Example : Object Type 에 따라, 몇 개의 Script 로 나눌 수 있습니다.
  SQL> Spool recompile.sql
  SQL> Select ‘alter ‘object_type’ ’object_name’ compile ; ’ From user_objects where status <> ‘VALID’ and object_type IN (‘VIEW’, ’SYNONYM’, ‘PROCEDURE’, ’FUNCTION’,     ‘PACKAGE’, ’TRIGGER’) ;
  SQL> spool off
  SQL> @ recompile.sql
  - Note : VIEW, SYNONYM, PROCEDURE, PACKAGE, FUNCTION, TRIGGER

  SQL> Spool pkg_body.sql
  SQL> Select ‘alter package ’object_name’ compile body ; ’ from user_objects where status <> ‘VALID’ and object_type = ‘PACKAGE BODY’ ;
  SQL> spool off
  SQL> @ pkg_body.sql
  - Note : PACKAGE BODY

  SQL> Spool undefined.sql
  SQL> select ‘alter materizlized view ’object_name’ compile ; ’ from user_objects where status <> ‘VALID’ and object_type =‘UNDEFINED’ ;
  SQL> spool off
  SQL> @ undefined.sql
  - Note : UNDEFINED

  SQL> Spool javaclass.sql
  SQL> Select ‘alter java class ’object_name’ resolve ; ’ from user_objects where status <> ‘VALID’ and object_type =‘JAVA CLASS’ ;
  SQL> spool off
  SQL> @ javaclass.sql
  - Note : JAVA CLASS’

  SQL> Spool typebody.sql
  SQL> Select ‘alter type ‘object_name’ compile body ; ’ from user_objects where status <> ‘VALID’ and object_type =‘TYPE BODY’ ;
  SQL> spool off
  SQL> @ typebody.sql
  - Note : TYPE BODY

  SQL> Spool public_synonym.sql
  SQL> Select ‘alter public synonym ‘object_name’ compile ; ’ from user_objects where status <> ‘VALID’ and owner = ‘PUBLIC’ and object_type = ‘SYNONYM’ ;
  SQL> spool off
  SQL> @ public_synonym.sql
  - Note : PUBLIC SYNONYM

2018년 8월 23일 목요일

Apache에 web 방화벽 설정하기

[ 참조 : https://www.manualfactory.net/  / http://sysadmin-notepad.blogspot.com/]

- 설치
# yum install mod_security mod_security_crs
# systemctl restart httpd

- 설치 후 webmail의 메일 내용 보이지 않고 권한 없다는 메세지 표시됨.
  에러 로그상의 Rule 번호 확인 후 apache에 해당 페이지에 Rule 제되 처리함.
# tail -f /var/log/httpd/modsec_audit.log
Message: Access denied with code 403 (phase 2). Pattern match "([\\~\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)\\-\\+\\=\\{\\}\\[\\]\\|\\:\\;\"\\'\\\xc2\xb4\\\xe2\x80\x99\\\xe2\x80\x98\\`\\<\\>].*?){4,}" at ARGS:_caps. [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_sql_injection_attacks.conf"] [line "159"] [id "981173"] [rev "2"] [msg "Restricted SQL Character Anomaly Detection Alert - Total # of special characters exceeded"] [data "Matched Data: = found within ARGS:_caps: pdf=1,flash=1,tiff=0,webp=0"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "8"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"]
Apache-Error: [file "apache2_util.c"] [line 271] [level 3] [client xx.xxx.xx.xx] ModSecurity: Access denied with code 403 (phase 2). Pattern match "([\\\\\\\\~\\\\\\\\!\\\\\\\\@\\\\\\\\#\\\\\\\\$\\\\\\\\%\\\\\\\\^\\\\\\\\&\\\\\\\\*\\\\\\\\(\\\\\\\\)\\\\\\\\-\\\\\\\\+\\\\\\\\=\\\\\\\\{\\\\\\\\}\\\\\\\\[\\\\\\\\]\\\\\\\\|\\\\\\\\:\\\\\\\\;\\\\"\\\\\\\\'\\\\\\\\\\\\xc2\\\\xb4\\\\\\\\\\\\xe2\\\\x80\\\\x99\\\\\\\\\\\\xe2\\\\x80\\\\x98\\\\\\\\`\\\\\\\\<\\\\\\\\>].*?){4,}" at ARGS:_caps. [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_sql_injection_attacks.conf"] [line "159"] [id "981173"] [rev "2"] [msg "Restricted SQL Character Anomaly Detection Alert - Total # of special characters exceeded"] [data "Matched Data: = found within ARGS:_caps: pdf=1,flash=1,tiff=0,webp=0"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "8"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [hostname "mail.qnct.cn"] [uri "/mail/"] [unique_id "W336PvEeYo-Q4P55eh7fCAAAABE"]
Action: Intercepted (phase 2)
Stopwatch: 1534982718653074 2421 (- - -)
Stopwatch2: 1534982718653074 2421; combined=1768, p1=215, p2=1521, p3=0, p4=0, p5=32, sr=74, sw=0, l=0, gc=0
Response-Body-Transformed: Dechunked
Producer: ModSecurity for Apache/2.9.2 (http://www.modsecurity.org/); OWASP_CRS/2.2.9.
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_wsgi/3.4 Python/2.7.5
Engine-Mode: "ENABLED"


# vi /etc/httpd/conf/httpd.conf

<Directory /var/www/webmail/>
    <IfModule mod_security2.c>
#       SecRuleEngine Off
        SecRuleRemoveById 981173
    </IfModule>
</Directory>
SecRuleEngine Off : 해당 Directory 적용 해제
SecRuleRemoveById 981173 : 해당 Directory에 특정 Rule만 해제

2018년 8월 9일 목요일

Centos7, RHEL 7 원격데스크톱 연결 XRDP 설치

[ 출처 : http://lionks80.blogspot.kr/ ]

1. 먼저 그놈 데스크톱을 설치한다.

- CentOS 7
# yum groupinstall "GNOME Desktop" "Graphical Administration Tools"
- RHEL 7
# yum groupinstall "Server with GUI"

2. 리눅스 런 레벨을 변경하여 GUI 인터페이스로 부팅한다.

# ln -sf /lib/systemd/system/runlevel5.target /etc/systemd/system/default.target

3. EPEL 업데이트 저장소에 추가한다.

- RPM 설치방법
# rpm -Uvh https://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
# rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-1.el7.nux.noarch.rpm
- Manual 설치 방법
# vi /etc/yum.repos.d/xrdp.repo

[xrdp]
name=xrdp
baseurl=http://li.nux.ro/download/nux/dextop/el7/x86_64/
enabled=1
gpgcheck=0

4. XRDP 설치

# yum -y install xrdp tigervnc-server

5. 서비스 시작 및 자동 시작

# systemctl start xrdp.service
# systemctl enable xrdp.service

6. 방화벽에 예외 추가

# firewall-cmd --permanent --zone=public --add-port=3389/tcp
# firewall-cmd --reload

7. SELinux 설정(Disabed 안되어 있을 경우)

# chcon --type=bin_t /usr/sbin/xrdp
# chcon --type=bin_t /usr/sbin/xrdp-sesman

8.Xwindows 언어설정

CentOS 7 의 기본 언어가 한글로 설정되어있다면 아래와 같이 언어 관련 설정을 추가합니다.
vi /etc/xrdp/startwm.sh
export LANG=ko_KR.utf8
** 실행 시 주의사항원격데스크톱 연결 시 색을 24bit 아래로 연결해야함.

리눅스 서버 60초안에 상황파악하기

[출처 : https://b.luavis.kr/ ]

넷플릭스의 Linux performance analysis in 60,000ms에 대해서 부분적으로 번역한 글입니다.
리눅스 서버에 성능 이슈로 인해서 로그인했을 때 우리가 가장 먼저 체크해봐야할 사항은 어떤것들일까?
넷플릭스는 많은 사람들이 알고 있는것 처럼 아마존 EC2 리눅스 서버를 사용하고 있다. 이런 대규모 서버를 모니터링하고 성능을 체크하는데에는 툴을 사용하는데 클라우드 전체의 모니터링에는 Atlas, 하나의 EC2 instance의 성능체크에는 Vector를 사용한다. 이 툴을 이용하면 대부분의 이슈는 해결 가능하지만, 가끔씩은 EC2 instance에 로그인해서 리눅스의 표준적인 성능 체크 툴을 사용할때가 있다.

첫 60초

이 글에서는 1분 안에 표준적인 리눅스 환경에서 CLI를 이용해 어떤 것들을 확인할지에 대해서 순서대로 알아볼것이다.
$ uptime
$ dmesg | tail
$ vmstat 1
$ mpstat -P ALL 1
$ pidstat 1
$ iostat -xz 1
$ free -m
$ sar -n DEV 1
$ sar -n TCP,ETCP 1
$ top
일부 커맨드는 sysstat package를 설치해야만한다. 이 측정법은 USE Method라고 불리는 병목현상이 생기는 위치를 찾는 방법의 일부분이다. USE는 CPU, memory, disk등의 모든 자원에 대해서 Utilization, saturation, error를 측정하는 방법이다.
아래 각각의 커맨드에 대한 설명에 붙어 있는 예제는 넷플릭스가 production 상태의 서버에서 측정한 값이다. 각 커맨드에 대한 더 많은 설명은 man 페이지를 이용해서 찾으면 된다.

1. uptime

$ uptime
23:51:26 up 21:31, 1 user, load average: 30.02, 26.43, 19.02

uptime은 현재 대기중인 프로세스가 얼마나 있는지를 나타내는 load average값을 확인하는 가장 쉬운 방법이다. 리눅스 시스템에서 이 값은 대기 중인 프로세스뿐만 아니라 disk I/O와 같은 I/O작업으로 block된 프로세스까지 포함되어 있다. 이를 통해서 얼마나 많은 리소스가 사용되고 있는지 확인할수 있지만, 정확하게 이해할 수는 없다.
위에 있는 3개의 숫자는 각각 1분, 5분, 15분에 load average 값이다. 이를 통해서 시간의 변화를 알 수 있는데, 예를들어서 장애가 발생했다는 소식을 듣고 해당 instance에 로그인 했을때 1분 동안의 값이 15분 값에 비해서 작다면 이는 장애가 발생하고선 내가 너무 뒤늦게 로그인했음을 알 수 있다. 위 예제에서는 1분 값이 약30이고 15분 값이 19정도 되는것으로 볼때 최근에 상승한것을 알 수 있다. 여기서 숫자가 큰것은 그만큼 CPU 사용량이 많다는것이고 뒤에 나오는 vmstat이나 mpstat같은 커맨드를 이용해서 확인할 수 있다.

2. dmesg | tail

$ dmesg | tail
[1880957.563150] perl invoked oom-killer: gfp_mask=0x280da, order=0, oom_score_adj=0
[...]
[1880957.563400] Out of memory: Kill process 18694 (perl) score 246 or sacrifice child
[1880957.563408] Killed process 18694 (perl) total-vm:1972392kB, anon-rss:1953348kB, file-rss:0kB
[2320864.954447] TCP: Possible SYN flooding on port 7001. Dropping request.  Check SNMP counters.

dmesg는 시스템 메세지를 확인할 수 있는 커맨드이다. 부팅시부터 시작해서 모든 커널메세지가 출력되기 때문에 tail을 이용해서 마지막 10줄만 출력한것이다. 이 메세지를 통해서 성능에 문제를 줄 수 있는 에러를 찾을 수 있는데 위의 예제에서는 oom-killer(out of memory)와 TCP request가 드랍된것을 알 수 있다.

3. vmstat 1

$ vmstat 1
procs ---------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
34  0    0 200889792  73708 591828    0    0     0     5    6   10 96  1  3  0  0
32  0    0 200889920  73708 591860    0    0     0   592 13284 4282 98  1  1  0  0
32  0    0 200890112  73708 591860    0    0     0     0 9501 2154 99  1  0  0  0
32  0    0 200889568  73712 591856    0    0     0    48 11900 2459 99  0  0  0  0
32  0    0 200890208  73712 591860    0    0     0     0 15898 4840 98  1  1  0  0
^C
virtual memory stat의 약자인 vmstat은 왠만한 환경에서 사용 가능한 툴이다. 1을 인자로 준 vmstat은 1초마다 정보를 보여준다. 첫번째 라인은 부팅된 뒤에 평균적인 값을 나타낸다.
확인해봐야할 항목
  • r: CPU에서 동작중인 프로세스의 숫자입니다. CPU 자원이 포화(saturation)가 발생하는지 확인할때에 좋은 값입니다. . r 값이 CPU의 값보다 큰 경우에 포화되어 있다고 해석된다.
  • free: free memory를 kb단위로 나타냅니다. free memory가 너무 자리수가 많은 경우 free -m를 이용하면 조금더 편하게 확인할 수 있다.
  • si, so: swap-in과 swap-out에 대한 값입니다. 0이 아니라면 현재 시스템에 메모리가 부족한것이다.
  • us, sy, id, wa, st: 모든 CPU의 평균적인 CPU time을 측정할 수 있다. 각각 user time, 커널에서 사용되는 system time, idle, wait I/O 그리고 stolen time순이다(stolen time은 hypervisor가 가상 CPU를 서비스 하는 동안 실제 CPU를 차지한 시간을 이야기한다.).

4. mpstat -p ALL 1

$ mpstat -P ALL 1
Linux 3.13.0-49-generic (titanclusters-xxxxx)  07/14/2015  _x86_64_ (32 CPU)

07:38:49 PM  CPU   %usr  %nice   %sys %iowait   %irq  %soft  %steal  %guest  %gnice  %idle
07:38:50 PM  all  98.47   0.00   0.75    0.00   0.00   0.00    0.00    0.00    0.00   0.78
07:38:50 PM    0  96.04   0.00   2.97    0.00   0.00   0.00    0.00    0.00    0.00   0.99
07:38:50 PM    1  97.00   0.00   1.00    0.00   0.00   0.00    0.00    0.00    0.00   2.00
07:38:50 PM    2  98.00   0.00   1.00    0.00   0.00   0.00    0.00    0.00    0.00   1.00
07:38:50 PM    3  96.97   0.00   0.00    0.00   0.00   0.00    0.00    0.00    0.00   3.03
[...]
이 커멘드는 CPU time을 CPU 별로 측정할 수 있다. 이 방법을 통하면 각 CPU별로 불균형한 상태를 확인할 수 있는데, 한 CPU만 일하고 있는것은 application이 single thread로 동작한다는 이야기다.

5. pidstat 1

$ pidstat 1
Linux 3.13.0-49-generic (titanclusters-xxxxx)  07/14/2015    _x86_64_    (32 CPU)

07:41:02 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
07:41:03 PM     0         9    0.00    0.94    0.00    0.94     1  rcuos/0
07:41:03 PM     0      4214    5.66    5.66    0.00   11.32    15  mesos-slave
07:41:03 PM     0      4354    0.94    0.94    0.00    1.89     8  java
07:41:03 PM     0      6521 1596.23    1.89    0.00 1598.11    27  java
07:41:03 PM     0      6564 1571.70    7.55    0.00 1579.25    28  java
07:41:03 PM 60004     60154    0.94    4.72    0.00    5.66     9  pidstat

07:41:03 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
07:41:04 PM     0      4214    6.00    2.00    0.00    8.00    15  mesos-slave
07:41:04 PM     0      6521 1590.00    1.00    0.00 1591.00    27  java
07:41:04 PM     0      6564 1573.00   10.00    0.00 1583.00    28  java
07:41:04 PM   108      6718    1.00    0.00    0.00    1.00     0  snmp-pass
07:41:04 PM 60004     60154    1.00    4.00    0.00    5.00     9  pidstat
^C

pidstat은 process당 top명령을 수행하는것과 비슷하다. 다만 차이점은 스크린 전체에 표시하는것이 아니라 지속적으로 변화하는 상황을 띄워주기 떄문에 상황변화를 기록하기 좋다.
위 예제를 보면 두개의 java process의 CPU 사용량이 엄청나다. %CPU 항목은 모든 CPU의 전체 사용량을 이야기한다. 따라서 1591%를 사용중인 java process들은 16CPU 가까이 사용중임을 나타내는것이다.

6. iostat -xz 1

$ iostat -xz 1
Linux 3.13.0-49-generic (titanclusters-xxxxx)  07/14/2015  _x86_64_ (32 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          73.96    0.00    3.73    0.03    0.06   22.21

Device:   rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
xvda        0.00     0.23    0.21    0.18     4.52     2.08    34.37     0.00    9.98   13.80    5.42   2.44   0.09
xvdb        0.01     0.00    1.02    8.94   127.97   598.53   145.79     0.00    0.43    1.78    0.28   0.25   0.25
xvdc        0.01     0.00    1.02    8.86   127.79   595.94   146.50     0.00    0.45    1.82    0.30   0.27   0.26
dm-0        0.00     0.00    0.69    2.32    10.47    31.69    28.01     0.01    3.23    0.71    3.98   0.13   0.04
dm-1        0.00     0.00    0.00    0.94     0.01     3.78     8.00     0.33  345.84    0.04  346.81   0.01   0.00
dm-2        0.00     0.00    0.09    0.07     1.35     0.36    22.50     0.00    2.55    0.23    5.62   1.78   0.03
[...]
^C

block device(HDD, SSD, …)가 어떻게 동작하는지 이해하기 좋은 툴이다.
확인해봐야할 항목
  • r/s, w/s rkB/s, wkB/s: read 요청과 write 요청, read kB/s, write kB/s를 나타낸다. 어떤 요청이 가장 많이 들어오는지 확인해볼 수 있는 중요한 지표다. 성능 문제는 생각보다 과도한 요청때문에 발생하는 경우도 있기 때문이다.
  • await: I/O처리 평균 시간을 밀리초로 표현한 값이다. application한테는 I/O요청을 queue하고 서비스를 받는데 걸리는 시간이기 때문에 application이 이 시간동안 대기하게 된다. 일반적인 장치의 요청 처리 시간보다 긴 경우에는 블럭장치 자체의 문제가 있거나 장치가 포화된 상태임을 알 수 있다.

7. free -m

$ free -m
             total       used       free     shared    buffers     cached
Mem:        245998      24545     221453         83         59        541
-/+ buffers/cache:      23944     222053
Swap:            0          0          0

확인해봐야할 항목
  • buffers: Block 장치 I/O의 buffer 캐시, 사용량
  • cached: 파일 시스템에서 사용되는 page cache의 양
위 값들이 0에 가까워 지면 안된다. 이는 곧 높은 Disk I/O가 발생하고 있음을 의미한다(iostat으로 확인 가능). 위 예제는 각각 59MB, 541MB로 괜찮은 정도에 속한다.
““-/+ buffers/cache”는 사용중인 메모리와 여유 메모리의 양을 나타낸다. 리눅스는 빠르게 다시 애플리케이션에 메모리가 할당될 수 있도록 캐시메모리를 사용한다. 따라서 캐시 메모리도 여유 메모리에 포함되어 보여야한다. 캐시메모리 또한 여유메모리로 계산하지 않는 착각으로 인해서 linuxatemyram란 사이트까지 있다. 다

8. sar -n DEV 1

$ sar -n DEV 1
Linux 3.13.0-49-generic (titanclusters-xxxxx)  07/14/2015     _x86_64_    (32 CPU)

12:16:48 AM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s   %ifutil
12:16:49 AM      eth0  18763.00   5032.00  20686.42    478.30      0.00      0.00      0.00      0.00
12:16:49 AM        lo     14.00     14.00      1.36      1.36      0.00      0.00      0.00      0.00
12:16:49 AM   docker0      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00

12:16:49 AM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s   %ifutil
12:16:50 AM      eth0  19763.00   5101.00  21999.10    482.56      0.00      0.00      0.00      0.00
12:16:50 AM        lo     20.00     20.00      3.25      3.25      0.00      0.00      0.00      0.00
12:16:50 AM   docker0      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
^C
이 툴을 사용하면 network throughput(Rx, Tx KB/s)을 측정할수 있다. 위 예제에서는 eth0의 수신량이 2Mbytes/s에 달했다. 이는 176Mbits/s인데 한계인 1Gbit/s에 아직 많이 못 미치는 값이다.
위 값중 %ifutilnicstat로도 측정 가능한 네트워크 장치 사용률이다. 하지만 nicstat에서도 그렇듯 정확한 값을 가져오는게 어려워서 위 예제에서도 잘 작동하지 않는다.

9. sar -n TCP,ETCP 1

$ sar -n TCP,ETCP 1
Linux 3.13.0-49-generic (titanclusters-xxxxx)  07/14/2015    _x86_64_    (32 CPU)

12:17:19 AM  active/s passive/s    iseg/s    oseg/s
12:17:20 AM      1.00      0.00  10233.00  18846.00

12:17:19 AM  atmptf/s  estres/s retrans/s isegerr/s   orsts/s
12:17:20 AM      0.00      0.00      0.00      0.00      0.00

12:17:20 AM  active/s passive/s    iseg/s    oseg/s
12:17:21 AM      1.00      0.00   8359.00   6039.00

12:17:20 AM  atmptf/s  estres/s retrans/s isegerr/s   orsts/s
12:17:21 AM      0.00      0.00      0.00      0.00      0.00
^C
이 값은 TCP 통신량을 요약해서 보여준다.
  • active/s: 로컬에서부터 요청한 초당 TCP 커넥션 수를 보여준다 (예를들어, connect()를 통한 연결).
  • passive/s: 원격으로부터 요청된 초당 TCP 커넥션 수를 보여준다 (예를들어, accept()를 통한 연결).
  • retrans/s: 초당 TCP 재연결 수를 보여준다.
active와 passive 수를 보는것은 서버의 부하를 대략적으로 측정하는데에 편리하다. 위 설명을 보면 active를 outbound passive를 inbound 연결로 판단할 수 있는데, 꼭 그렇지만은 않다. (예를들면 localhost에서 localhost로 연결같은 connection)
retransmits은 네트워크나 서버의 이슈가 있음을 이야기한다. 신뢰성이 떨어지는 네트워크 환경이나(공용인터넷), 서버가 처리할 수 있는 용량 이상의 커넥션이 붙어서 패킷이 드랍되는것을 이야기한다. 위 예제에서는 초당 하나의 TCP 서버가 들어오는것을 알 수 있다.

10. top

$ top
top - 00:15:40 up 21:56,  1 user,  load average: 31.09, 29.87, 29.92
Tasks: 871 total,   1 running, 868 sleeping,   0 stopped,   2 zombie
%Cpu(s): 96.8 us,  0.4 sy,  0.0 ni,  2.7 id,  0.1 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:  25190241+total, 24921688 used, 22698073+free,    60448 buffers
KiB Swap:        0 total,        0 used,        0 free.   554208 cached Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 20248 root      20   0  0.227t 0.012t  18748 S  3090  5.2  29812:58 java
  4213 root      20   0 2722544  64640  44232 S  23.5  0.0 233:35.37 mesos-slave
 66128 titancl+  20   0   24344   2332   1172 R   1.0  0.0   0:00.07 top
  5235 root      20   0 38.227g 547004  49996 S   0.7  0.2   2:02.74 java
  4299 root      20   0 20.015g 2.682g  16836 S   0.3  1.1  33:14.42 java
     1 root      20   0   33620   2920   1496 S   0.0  0.0   0:03.82 init
     2 root      20   0       0      0      0 S   0.0  0.0   0:00.02 kthreadd
     3 root      20   0       0      0      0 S   0.0  0.0   0:05.35 ksoftirqd/0
     5 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:0H
     6 root      20   0       0      0      0 S   0.0  0.0   0:06.94 kworker/u256:0
     8 root      20   0       0      0      0 S   0.0  0.0   2:38.05 rcu_sched

top 명령어는 위에서 체크해본 다양한 측정치를 쉽게 체크할 수 있다. 시스템 전반적으로 값을 확인하기 쉽다는 장점이 있다. 화면이 지속적으로 바뀌는 점 떄문에 패턴을 찾는것이 어렵다. 일시적으로 멈추는 현상을 잡기 위해서도 화면을 주기적으로 빠르게 멈춰주지 않으면 찾기 힘들다(Ctrl+S는 업데이트를 중지시키고, Ctrl+Q는 다시 시작시킨다), 그리고 화면이 지워져버린다.

2018년 7월 26일 목요일

SAP GUI 스크립트

[출처 : http://snowxtal.egloos.com/ ]

오늘 얘기하고 싶은 주제는 Excel VBA를 이용하여 SAP를 자동화하는 방법입니다. 우선 이게 뭔 소린인지부터 설명을 해야겠네요. SAP는 기업에서 사용하는 전사적 자원관리(Enterprise Resource Planning, ERP) 소프트웨어 중에서 세계시장 점유율이 가장 높은 제품입니다. 알기 쉽게 좀 과장해서 말하면 판매부서에서 제품 주문을 받아 시스템에 입력하면, 자동으로 생산부서에 통보가 날라 가고 그 주문량에 맞춰서 원료사용계획과 생산인력 할당이 이루어지고, 선적 계획과 수금 계획까지 잡히고, 이후 실행상태를 실시간으로 확인할 수 있는 그런 시스템이죠. 대부분의 기업에서 필요한 자료구조와 업무절차를 처리할 수 있도록 만들어 놓은 기업용 솔루션입니다. 대부분의 세계적인 다국적기업들이 SAP를 쓰고 있습니다.

근데 뭔가 되게 근사한 거 같긴 한데 막상 이거 설치해 놓고 쓰라고 하면 사용자들 대부분이 욕부터 합니다. 유저 인터페이스가 아주아주 거지같기 때문이죠. 제품번호는 다 번호로 되어 있고, 공장이름도 번호로 되어 있고, 거래처도 번호로 되어 있고, 심지어 명령어도 mb51 등의 아주아주 기억하기 힘든 코드로 되어 있는데 이런 외우기 힘든 암호들을 이용해서 업무를 처리하라고 강요합니다. 서로 다른 기업에서 공통적으로 사용할 수 있도록 하다보니 사업장/제품/거래처 등에 직접적인 이름을 사용하는 것이 아니라 숫자를 붙여 구분하도록 만들어진거죠. 친절한 메뉴와 직관적인 인터페이스에 익숙한 대부분의 윈도우즈 프로그램 사용자들은 적응하는데 상당한 시간이 걸립니다. 말하자면 아래아 한글에 익숙한 사람한테 이제부터 리눅스 vi 에디터를 사용해서 문서를 편집하라고 강요하는 식입니다.

하여튼 가뜩이나 번호도 외우기 힘든데 거기다가 기업업무의 특성상 비슷한 일을 반복해야 하는 일이 아주 잦다는 겁니다. 즉, 주문 입력하는 넘은 제품번호와 고객번호만 다른 수백건의 주문들을 허구헌날 입력해야 한다는 거고, 생산하는 넘은 비슷비슷한 번호의 수십-수백개의 제품과 원료를 양을 허구헌날 체크해서 망할 부장한테 엑셀로 챠트를 만들어 가야 한다는 겁니다. 이쯤되면 쓰기 익숙한 엑셀에다 주문정보를 입력한 다음 한 큐에 SAP에 입력하는 방법이나, 재고 확인해야 하는 원료를 엑셀에다 저장해 놨다가 버튼 하나 띡 누르면 SAP로부터 값을 쭈루룩 뽑아오는 프로그램이 없을까 하고 고민을 하게 되죠.이럴 때 필요한 것이 SAP GUI 스크립팅이라는 겁니다.

먼저 개념을 좀 잡아 보죠. SAP는 크게 내부 데이타베이스 계층, 어플리케이션 계층 그리고 유저인터페이스 계층으로 구분할 수 있습니다. 데이타베이스와 어플리케이션 계층은 사용자에게는 노출되지 않고 서버에서 백그라운드로 돌아가는 프로세스이므로 유저가 직접적으로 제어하기 어렵습니다. 사용자들은 웹 브라우저를 이용해서 인터넷에 접근하듯이 SAP GUI라는 클라이언트 프로그램을 이용하여 백그라운드의 데이타베이스 및 어플리케이션 서비스를 구동하게 됩니다. 저희가 하고자 하는 것은 이 SAP GUI라는 클라이언트 프로그램을 자동화하는 것입니다.

SAP GUI의 외부 제어를 가능하게 하는 두 가지 요소는 SAP GUI가 내장하고 있는 COM 인터페이스와 스크립트 레코더입니다. 엑셀과 마찬가지로 SAP에도 스크립트 언어가 있습니다. SAP 내부의 데이타베이스와 어플리케이션 계층을 제어하는 언어는 ABAP이라는 언어인데, 다행스럽게도 유저가 대면하는 SAP GUI는 ABAP이 아닌 visual basic을 기반으로 하는 스크립트 언어를 제공합니다. SAP GUI를 제어하는 것은 다음의 3단계로 이루어집니다.

1. SAP 스크립트 레코더를 이용하여 SAP의 단위업무를 기록(.vbs 화일 생성)
2. 기록된 스크립트를 엑셀 VBA 에디터로 옮겨 modify
3. 엑셀에서 매크로를 실행하여 SAP를 자동으로 구동

그럼 이제부터 어떻게 하면 엑셀과 SAP를 이용해서 업무를 자동화할 수 있는지 차근차근 알아보도록 하겠습니다.

1. SAP 스크립트 기록
스크립트 레코더는 SAP GUI의 메뉴 아래 툴바 맨 오른쪽의 스크린 모양 아이콘을 눌러보면 나옵니다.

엑셀의 매크로 리코더와 마찬가지로 기록 버튼을 누르고 나면 사용자가 SAP GUI 상에서 수행하는 여러 작업들이 VB형식의 스크립트로 텍스트 화일에 저장되게 됩니다. TR-CODE는 가능하면 명령어 입력란(아래 그림 참조)에서 타이핑하시는 편이 코딩하기 편하고 수정하기도 쉽습니다. tr-code msc03 앞에 있는 "/n"은 현재 작업내용을 모두 취소하고 그 뒤에 따라오는 tr-code를 수행하라는 뜻입니다. tr-code입력 후 단위 작업을 마치고 기록중지 버튼을 누른 후 경로상의 vbs화일을 열어 보면 방금 행한 작업들이 vb명령어의 형태로 적혀 있는 것을 보실 수 있습니다. 재생 버튼을 누르면 기록된 스크립트가 윈도우즈 스크립트 호스트에 의해 수행되어 SAP를 구동하게 됩니다.


2. 기록된 스크립트를 편집
기록된 스크립트를 메모장을 이용해 열어보면 크게 두 가지 부분으로 구성된 것을 볼 수 있습니다. 앞 부분은 SAP GUI의 COM객체를 생성하여 연결하는 부분입니다. 이 부분은 모든 자동화 스크립트에 공통적인 것이라서 항상 같은 내용이 기록됩니다. 그리고 뒷부분은 실제 유저가 수행한 작업들이 기록됩니다.
작업내용을 보면 명령어 입력란에 "/nmsc3n"이라는 명령어를 입력하고, 화면상의 각 요소에 어떤어떤 값들을 넣고 마지막으로 실행(sendvkey 0)을 눌렀다는 식으로 기록되어 있습니다. 실제 작업에서는 불필요한 명령어도 좀 포함되어 있죠. 이것을 엑셀로 그대로 옮겨서 내가 짠 매크로에 첨가하면 됩니다. 단, SAP 객체를 생성하고 연결하는 앞 부분은 엑셀에 맞게 좀 수정해 줘야 하는데 아래와 같이 바꿔주면 됩니다. 이 부분은 매번 동일한 코드를 삽입하면 되기 때문에 한번 예제를 만들어 둔 이후에는 작업기록 부분만 수정해서 쓰시면 됩니다.

    'vbs의 SAP연결 코드는 아래와 같이 변경해 주시면 됩니다.
    Dim rotEntry, guiApp, connection, session

    Set rotEntry = GetObject("SAPGUI")
    Set guiApp = rotEntry.GetScriptingEngine
    Set connection = guiApp.Children(0)
    Set session = connection.Children(0)


    '작업내용 부분은 vbs 화일에서 복사해 오면 됩니다.
    session.findById("wnd[0]").maximize
    session.findById("wnd[0]/tbar[0]/okcd").Text = "/nmsc3n"
    session.findById("wnd[0]").sendVKey 0
    ...

3. 엑셀에서 매크로를 구동
이제 코드가 다 작성되었으면 실행을 하면 됩니다. 작성된 코드는 버튼을 클릭할 때 동작하도록 하는 것이 가장 일반적이겠지요. 매크로는 SAP GUI가 실행된 상태에서 구동해야 합니다. 실행되고 있지 않으면 객체를 찾을 수 없어 매크로에서 런타임 에러가 납니다. 제대로 연결되면 SAP에서 아래와 같은 보안 메세지가 뜹니다. OK를 누르시면 SAP가 매크로에 기록된 작업을 수행하면서 화면이 차례로 바뀌는 것을 볼 수 있습니다.

자 이제 개념은 대략 잡히셨을거라 생각합니다. SAP 작업내용을 기록하고, 엑셀로 옮겨서 수정한 후 실행시킨다.. 이거죠. 근데 이상의 과정을 진행하는데 접하실 수 있는 몇 가지 문제를 짚고 넘어 가겠습니다.

곤란한 상황 1.
SAP의 스크립트 레코더가 유저한테 노출되지 않은 경우가 더러 있습니다. SAP GUI의 메뉴 아래 툴바 맨 오른쪽에 보면 스크린 모양의 아이콘이 있는데 이걸 선택하여 스크립트 레코더가 켜져 있는지 확인하시기 바랍니다.
곤란한 상황 2.
가끔은 서버 측에서 스크립트 사용을 막아놓은 경우가 있습니다. 그럴 경우엔 시스템 관리자에게 열어달라고 요청해야 합니다.

응용예 1.

이제 응용부분을 좀 생각해 보죠. 어떤 작업을 기록하고 한번만 반복하는 건 사실 별 의미가 없습니다. 수십번 반복해서 해야될 일을 엑셀과 SAP에게 맡겨 놓고 나는 띵까띵까 놀 수 있으면 좋겠죠. 그럼 반복작업을 어떻게 구현할 수 있을지 살펴보겠습니다. 아래의 예제는 material 넘버를 쭉 나열해 놓고 버튼을 누르면 각 물질의 description을 SAP에서 불러오는 예제입니다.
아래 루틴은 버튼이 눌려질 때 수행되는 코드입니다.

Private Sub DescButton_Click()
    Dim rotEntry, guiApp, connection, session, grid, t
    Dim i As Integer, r As Range
  
    On Error GoTo Err_Handler
  
    Application.WindowState = xlMinimized
  
    ' SAP 연결 부분
    Set rotEntry = GetObject("SAPGUI")
    Set guiApp = rotEntry.GetScriptingEngine
    Set connection = guiApp.Children(0)
    Set session = connection.Children(0)

   
    '사용자 작업부분
    session.findById("wnd[0]").maximize
    session.findById("wnd[0]/tbar[0]/okcd").Text = "/nmm03" 'mm03은 물질정보를 보는 tr-code
    session.findById("wnd[0]").sendVKey 0
  
    Set r = [A3]
    r.Offset(, 1).Resize(1000).ClearContents '데이타 영역 초기화
    Do While r <> ""
        session.findById("wnd[0]/usr/ctxtRMMG1-MATNR").Text = r.Value '물질번호 입력
        session.findById("wnd[0]").sendVKey 0 '실행

        'SAP의 description 컨트롤 항목에서 값을 읽어 옴.
        Set t = session.findById("wnd[0]/usr/tabsTABSPR1/tabpSP01/ssubTABFRA1:SAPLMGMM:2004/subSUB1:SAPLMGD1:1002/txtMAKT-MAKTX")
        r.Offset(, 1) = t.Text
        session.findById("wnd[0]").sendVKey 3
        Set r = r.Offset(1)
    Loop
    session.findById("wnd[0]").sendVKey 3
    Application.WindowState = xlMaximized
    MsgBox "Done!"
  
    Exit Sub
  
Err_Handler:
    MsgBox Err.Description, , "Error"

End Sub

회사마다 SAP가 조금씩 customize되어 있을 수 있기 때문에 제가 짠 매크로가 그대로 동작하지는 않을 수도 있습니다. 하지만 앞에서 말씀드린 개념을 잘 이해하신 분이라면 분석하시는데 큰 어려움이 없으실 겁니다. 매크로는 일단 SAP를 연결하고, mm03 transaction을 돌립니다. 그리고 [A3]부터 시작하여 공란이 나올 때까지 순차적으로 아래로 내려 가면서 각 셀의 값을 SAP에 입력하고, 결과 화면에서 description 부분을 읽어서 그 옆 칸에 적어넣는 거죠. 그런데 내가 원하는 값이 있는 SAP 상의 화면요소의 이름을 어떻게 알아내는지 궁금하시죠? 간단합니다. 매크로 레코딩할 때 결과화면에서 그 화면요소에 커서를 찍으면 기록된 매크로에 해당 화면요소의 이름이 같이 기록되어 나옵니다. 요렇게요.

session.findById("wnd...(중략).../txtMAKT-MAKTX").setFocus
session.findById("wnd...(중략).../txtMAKT-MAKTX").caretPosition = 39

그 화면요소의 text 프로퍼티를 읽어오면 되지요. 위의 예제 화일을 첨부하였으니 필요에 따라 수정해서 사용하시기 바랍니다.
SAP_example1.xlsm

응용예 2.

SAP의 결과 화면들은 가끔씩 테이블 형태로 나타납니다. 테이블 형태가 아닌 텍스트 화면들도 레이아웃을 잘 선택하면 테이블 형태로 바뀌는 경우도 꽤 있습니다. 이 테이블 형태의 자료는 컬럼이름과 행번호를 이용해 접근할 수 있습니다. 스크립트 리코더에서 결과 화면을 출력한 후 각 컬럼에 커서를 놓으면 해당 컬럼의 이름이 스크립트에 기록됩니다. 이것을 이용해 다음과 같은 방법으로 값을 읽어올 수가 있는 거죠.

        'extract data from table
        Set grid = session.findById("wnd[0]/usr/cntlGRID1/shellcont/shell")
        Set r = [A2]

        For i = 0 To grid.RowCount - 1
            r.Offset(, 1) = grid.getcellvalue(i, "WERKS") 'plant
            r.Offset(, 2) = grid.getcellvalue(i, "MATNR") 'material
            r.Offset(, 3) = grid.getcellvalue(i, "CHARG") 'batch
            r.Offset(, 4) = grid.getcellvalue(i, "ATNAM") 'char. name
            r.Offset(, 5) = grid.getcellvalue(i, "ATBEZ") 'char. description
            r.Offset(, 6) = grid.getcellvalue(i, "GV_OUTPUT_VALUE") 'value
           
            Set r = r.Offset(1)
        Next

테이블 화면요소를 grid라는 object로 할당해 놓고, 이후에 grid.getcellvalue(row_no, col_name) 형식으로 읽을 수 있습니다.

맺음말
저는 도대체 어디서 이런 정보들을 얻었을까요? 답은 아주 쉽습니다. SAP 툴바에서 스크립트 리코더를 실행시키는 메뉴 아래쪽을 보면 SAP GUI Help 등 프로그래밍 관련 정보가 나옵니다. 거길 살펴 보시면 SAP session을 연결하는 방법, 테이블 등 여러 가지 화면요소와 관련된 클래스/프로퍼티 등등 아주 많은 관련 정보들이 나옵니다. 그걸 참조하셔서 응용하시면 되는 거지요. 사실 위에 말씀드린 예제들만 응용하셔도 단순무식한 반복작업들을 상당히 줄일 수 있습니다. 그럼 본 포스팅이 귀사의 업무에 도움이 되셨길 바라며 장황한 글을 줄입니다.

p.s. 가끔 댓글로 도움 요청하시는 분들이 계시는데 제가 워낙 게을러서 댓글 자주 안 봅니다. 성질도 나빠서 댓글 봐도 잘 안 도와줍니다. ㅋㅋ 가끔 기분 내킬 때 답글 달 때도 있지만 너무 기대하지 마세요. 사실 이 정도 알려드렸으면 본인 스스로 찾아서 해결하셔야죠.

2018년 5월 28일 월요일

Windows에서 Bat로 ftp 파일 가져오기

[ 출처 : http://cafe.naver.com/ ]

Windows에서 Bat로 ftp 파일 가져오기

open xx.xx.xx.xx
ftpid xx
ftppasswd xxxx
bin
hash
prompt
put "x:\xxxx\xxxx\xxxx.dmp"
quit

2018년 5월 16일 수요일

Windows에서 날짜 시간을 파일명으로 쓰기

[출처 : http://www.dreamy.pe.kr/ ]

[방법 1]
@set YEAR=%date:~0,4%
@set MONTH=%date:~5,2%
@set DAY=%date:~8,2%
@set HOUR=%time:~0,2%
@set MINUTE=%time:~3,2%
@set SECOND=%time:~6,2%

@set POSTFIX=%YEAR%-%MONTH%-%DAY%_%HOUR%-%MINUTE%-%SECOND%

mkdir "log_%POSTFIX%"
cd "log_%POSTFIX%"


[방법 2]
@set YEAR=%date:~0,4%
@set MONTH=%date:~5,2%
@set DAY=%date:~8,2%
@set HOUR=%time:~0,2%
@set MINUTE=%time:~3,2%
@set SECOND=%time:~6,2%

@set POSTFIX=%YEAR%-%MONTH%-%DAY%_%HOUR%-%MINUTE%-%SECOND%

mkdir "log_%POSTFIX%"
cd "log_%POSTFIX%"

추가 Cammand창의 언어 코드 변환하기
CHCP 437  <-- 영문으로 변경
CHCP 949  <-- 한글로 변경

[출처 :  https://serverfault.com/ ]
파워셀을 이용한 방식

@echo off
for /f %%i in ('powershell ^(get-date^).DayofWeek') do set week=%%i
echo %week%

*Output
Sunday

#현재일에서 하루를 빼 줄 경우 
for /f %%i in ('powershell ^(get-date^).AddDay(-1)') do set week=%%i
echo %week%

* Output
Saturday

#현재일자 출력 포맷 변경시
for /f %%i in ('powershell ^(get-date -uFormat %y%m%d^).AddDay(-1)') do set date=%%i
echo %date%

* Output
191001

2018년 5월 15일 화요일

[SAP] ABAP Query 사용자 Lock 해제 방법

[ 출처 : https://archive.sap.com/ ]

Tcode SQ02에서 대상 InfoSet을 선택한 후 상단 메뉴의 Goto-Query directory 선택
















해당 InfoSet이름으로 조회




























Lock를 삭제하고자하는 Query를 선택하고 해제 처리









해제 후 사용자 편집 가능함.

2018년 5월 7일 월요일

Linux 일반 사용자가 Sudo로 관리자 권한 사용하도록 설정

[출처 : http://taewan.kim/ ]

사용자 관리하는 파일의 기본 권한은 읽기 전용이므로 쓰기 권한 부여 후 사용자 추가
# ll /etc/sudoers
-r--r-----. 1 root root 3958 May  7 10:45 sudoers
# chmod 662 /etc/sudoera
# ll /etc/sudoers
-rw-rw--w-. 1 root root 3958 May  7 10:45 sudoers

설정 파일의 끝부분에 추가할 사용자 및 Group에 대하여 추가함.
#vi /etc/sudoera
root    ALL=(ALL)       ALL
{newuser}   ALL=(ALL)       ALL

{newuser}   ALL=(ALL)       NOPASSWD:  ALL   <-- 패스워드 생략시


%{group}   ALL=(ALL)       ALL
%{group}   ALL=(ALL)       NOPASSWD:  ALL   <-- 패스워드 생략시


설정 저장 후 파일 권한 설정을 원래대로 되돌림.
 # chmod 440 /etc/sudoera

2018년 3월 8일 목요일

아두이노의 수집된 정보를 서버에 저장 하는 방법

[출처 : http://arduinotronics.blogspot.kr/ ]

Build your own IOT service! Collect sensor data and send it to a web/database server.

Today's project uses an Arduino equipped with a Ethernet shield, and a DHT-11 temperature / humidity sensor.


  Arduino UNO
  Arduino Ethernet Shield
  DHT-11 Module

The Arduino reads the DHT-11, and submits the data to a php script on a hosted web server. That php page inserts the data into a mySQL database, and another php page creates a web page displaying the data as you can see below.

(ESP8266 / BME280 Version)
(UNO / WiFi BME280 Version)

- Arduino 부분
It's Alive, It's Alive. Ok, sounds better if done with a Dr. Frankenstein accent, but the Arduino WiFi wireless weather Server is alive. Starting with a Arduino UNO, we then stacked a Arduino WiFi shield, a adafruit Lithium Polymer battery shield, and a Sparkfun Protoshield with a Embedded Adventures BME280 breakout and a 3.3v - 5v level shifter. A 5v solar panel is on it's way to keep this charged,

Arduino UNO
Arduino WiFi
Adafruit LIPO
Sparkfun Protoshield
Embedded Adventures BME280 (schematics)
Embedded Adventures Level Shifter



Code (Video below)

#include <SPI.h>
#include <WiFi.h>


#include <BME280_MOD-1022.h>

#include <Wire.h>

IPAddress dns(192, 168, 254, 254);
IPAddress ip(192, 168, 254, 16); 
IPAddress gateway(192, 168, 254, 254);
IPAddress subnet(255, 255, 255, 0);

float temp, humidity,  pressure, pressureMoreAccurate, tempF, inHg, rH;
double tempMostAccurate, humidityMostAccurate, pressureMostAccurate;


char ssid[] = "your ssid";      // your network SSID (name)
char pass[] = "your password";   // your network password
int keyIndex = 0;                 // your network key Index number (needed only for WEP)

int status = WL_IDLE_STATUS;

WiFiServer server(80);

// print out the measurements

void printCompensatedMeasurements(void) {

char buffer[80];

  temp      = BME280.getTemperature();
  humidity  = BME280.getHumidity();
  pressure  = BME280.getPressure();
 
  pressureMoreAccurate = BME280.getPressureMoreAccurate();  // t_fine already calculated from getTemperaure() above
 
  tempMostAccurate     = BME280.getTemperatureMostAccurate();
  humidityMostAccurate = BME280.getHumidityMostAccurate();
  pressureMostAccurate = BME280.getPressureMostAccurate();

  Serial.print("Temperature  ");

  tempF = tempMostAccurate * 1.8 + 32.0;
  Serial.print(tempF);

  Serial.print(" ");
  Serial.print(char(176));
  Serial.println("F");
 
  Serial.print("Humidity     ");

  rH = humidityMostAccurate;
  Serial.print(rH);
  Serial.println(" %");

  Serial.print("Pressure     ");

  inHg = pressureMostAccurate * 0.0295299830714;
  Serial.print(inHg, 2);
  Serial.println(" in. Hg");
}


void setup() {
  Wire.begin();
 
  //Initialize serial and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    // don't continue:
    while (true);
  }

  String fv = WiFi.firmwareVersion();
  if ( fv != "1.1.0" )
    Serial.println("Please upgrade the firmware");

  // attempt to connect to Wifi network:

 
  WiFi.config(ip, dns, gateway, subnet);
 
  while ( status != WL_CONNECTED) {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);

    // wait 10 seconds for connection:
    delay(10000);
  }
  server.begin();
  // you're connected now, so print out the status:
  printWifiStatus();
}


void loop() {
 
  uint8_t chipID;
 
  chipID = BME280.readChipId();
 
  // find the chip ID out just for fun
  //Serial.print("ChipID = 0x");
  //Serial.print(chipID, HEX);
 

  // need to read the NVM compensation parameters
  BME280.readCompensationParams();
 
  // Need to turn on 1x oversampling, default is os_skipped, which means it doesn't measure anything
  BME280.writeOversamplingPressure(os1x);  // 1x over sampling (ie, just one sample)
  BME280.writeOversamplingTemperature(os1x);
  BME280.writeOversamplingHumidity(os1x);
 
  // example of a forced sample.  After taking the measurement the chip goes back to sleep
  BME280.writeMode(smForced);
  while (BME280.isMeasuring()) {
    Serial.println("Measuring...");
    delay(50);
  }
  Serial.println("Done!");
 
  // read out the data - must do this before calling the getxxxxx routines
  BME280.readMeasurements();
 
  // Example for "indoor navigation"
  // We'll switch into normal mode for regular automatic samples
 
  BME280.writeStandbyTime(tsb_0p5ms);        // tsb = 0.5ms
  BME280.writeFilterCoefficient(fc_16);      // IIR Filter coefficient 16
  BME280.writeOversamplingPressure(os16x);    // pressure x16
  BME280.writeOversamplingTemperature(os2x);  // temperature x2
  BME280.writeOversamplingHumidity(os1x);     // humidity x1
 
  BME280.writeMode(smNormal);
  
  while (1) {
   
   
    while (BME280.isMeasuring()) {


    }
   
    // read out the data - must do this before calling the getxxxxx routines
    BME280.readMeasurements();
    printCompensatedMeasurements();
   
    delay(2000); // do this every 5 seconds
    Serial.println();
 
 
  // listen for incoming clients
  WiFiClient client = server.available();
  if (client) {
    Serial.println("new client");
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (c == '\n' && currentLineIsBlank) {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");  // the connection will be closed after completion of the response
          client.println("Refresh: 5");  // refresh the page automatically every 5 sec
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          // output the value of each sensor

            client.print("Temperature ");
            client.println(tempF);
            client.print("&deg;");
            client.print("F");
            client.println("<br />");
            client.print("Humidity ");
            client.println(rH);
            client.print(" %");
            client.println("<br />");
            client.print("Pressure ");
            client.println(inHg);
            client.print(" in. Hg");
            client.println("<br />");
         
          client.println("</html>");
          break;
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
        }
        else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);

    // close the connection:
    client.stop();
    Serial.println("client disonnected");
  }
}


}

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);




  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}









- 서버 설정 [Source]
 Last week we connected a ICStation BME280 temperature / humidity / barometric pressure sensor to a a ICStation NodeMCU ESP8266. We displayed the collected data (along with Dew Point and Heat Index calculations) in the serial monitor.

This week we modified the sketch to post those variables to a linux server (could be your own local Raspberry Pi) running MySQL and PHP. We have it set to take a reading every 30 seconds, and post the data to a php page that inserts the data into the MySQL database. The index page displays a table of that data. The time and date stamp has been modified to display the data in the timezone of the location of the sensor. We are working on live gauges and graphs to display this data in real time.''

See the live data at http://theiot.zone/templog/

All code can be downloaded from https://drive.google.com/open?id=0ByRIq5k2wjcSXzVvamZ1dk9yQVU

Thanks to Nuno Santos and his tutorial at https://techtutorialsx.com/2016/07/21/esp8266-post-requests/ for some fine tuning of my code.