레이블이 curl인 게시물을 표시합니다. 모든 게시물 표시
레이블이 curl인 게시물을 표시합니다. 모든 게시물 표시

nginx 2개의 조건 체크

# nginx 의 if 는 2개 이상의 조건을 (and, or) 사용할 수 없다.
# 이 경우 스트링 변수를 만들어 각 조건이 참일 경우 값을 추가하고
# 최종 이 스트링 변수를 확인하는 방식으로 처리할 수 있다.

# 예제
# user-agent 가 IE 이고 myurl 파라메터가 포함된 요청은 그대로 두고(http->http)
# 그 외의 요청들은 모두 https 로 리다이렉트 한다.(http->https)
# nginx.conf 설정
server {
    listen          [::]:80;
    server_name     ysoftman.test.com;

    set $ok "";
    # ua 가 IE 가 아닌 경우
    if ( $http_user_agent !~* "(msie \d\d?|rv:11|Trident/7.0)") {
        set $ok "noie";
    }
    # myurl=http(s)://xxx 파라메터가 없는 경우
    if ( $arg_myurl !~* "^https?.*" ) {
        set $ok "${ok}nomyurl";
    }
    # ua가 IE 가 아니거나 myurl 파라메터가 아니면 https 로 리다이렉트
    if ( $ok ~* "(noie|nomyurl)") {
        return 302 https://ysoftman.test.com$request_uri;
    }
}

# http -> http 로 변경 없는 예시
curl 'http://ysoftman.test.com/myurl=https://www.google.com' -H 'User-Agent: msie 10 bbbb' -v

# http -> https 로 리다이렉트 되는 예시 (noie)
curl 'http://ysoftman.test.com/myurl=https://www.google.com' -v

# http -> https 로 리다이렉트 되는 예시 (nomyurl)
curl 'http://ysoftman.test.com/' -H 'User-Agent: msie 10 bbbb' -v

# 참고

js fetch() with credntial 옵션 사용시 CORS 에러 방지 서버 설정

# javascript(js) fetch 함수 요청시 CORS(Cross-Origin Resource Sharing) 에러 방지하기
# js fetch() 로 ysoftman1 에서 ysoftman2 로 요청하는 상황으로 다음과 같다.
# 크롬 개발자 도구 console 에서 실행
fetch("http://ysoftman2/lemon", {
  "headers": {
    "accept": "application/json, text/plain, */*",
    "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
    "authorization": "Bearer aaabbbccc123456789",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-site"
  },
  "referrer": "https://ysoftman1/apple",
  "referrerPolicy": "no-referrer-when-downgrade",
  "body": null,
  "method": "GET",
  "mode": "cors",
  "credentials": "include"
}).then(response => response.json());

# 이때 요청 흐름은 js fetch() --> browser --> server 이 된다.
# browser 는 OPTIONS 메소드로 서버에게 접근 가능한지 물어본다.(preflight 과정)
# 이때 서버는 응답 헤더에 다음 값들을 설정할 수 있고
Access-Control-Allow-Origin: 접근 가능한 호스트
Access-Control-Allow-Methods: 접근 가능한 메소드
Access-Control-Allow-Headers: 접근 가능한 헤더

# browser 는 이 허용된 값들 내에서 서버에 실제 요청을 하게 된다.

# 서버가 와일드카드(Access-Control-Allow-Origin: *)를 사용하더라도 
# 클라가 credential (ajax, xmlhttp 로 다른도메인에 쿠키를 설정할때 사용하는 옵션)를 사용하면 CORS 정책으로 블럭된다.
# 다음과 같이 exact 한 호스명과 vary (브라우저가 캐시 사용시 어떤 헤더를 보고 구분해야 하는지 알려준다) 헤더에 origin 을 설정해야 한다.
Access-Control-Allow-Origin: https://developer.mozilla.org
Vary: Origin

# 서버쪽 설정 방법들
# 방법1 - k8s ingress 사용시, enable-cors true 설정(디폴트 false)
# enable-cors 를 사용하면 cors 관련 응답헤더들(Access-control-xxx)이 자동으로 추가된다.
# 그리고 Access-Control-Allow-Origin 가 * 로 고정된다.
# configuration-snippet 의 Access-Control-xxx 헤더가 응답에 중복되어
# cors 가 동작하지 않으니 같이 사용하지 않는다.
metadata
  annotations:
    ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-headers: "x-test-header"
    
# 방법2 - k8s ingress 사용시, configuration-snippet 로 설정
metadata
  annotations:
    ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    # configuration-snippet 현재 location 의 설정에 추가된다.
    nginx.ingress.kubernetes.io/configuration-snippet: |
      if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        add_header 'Access-Control-Max-Age' "1728000";
        add_header 'Content-Type' "text/plain charset=UTF-8";
        add_header 'Content-Length' "0";
        add_header 'Vary' "Origin";
        return 204;
      }
      add_header 'Access-Control-Allow-Origin' "$http_origin";
      add_header 'Access-Control-Allow-Credentials' "true";
      add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
      add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
      add_header 'Vary' "Origin";

# 방법3 - k8s ingress 사용시, 특정 도메인에만 cors 허용할때 변수를 설정해 구분
metadata
  annotations
    nginx.ingress.kubernetes.io/configuration-snippet: |
      if ($http_origin ~* "^https?:\/\/(.*\.)?((ysoftman\-lemon\.com)|(ysoftman\-apple\.com))$") {
        set $cors = "cors";
      }
      if ($request_method = 'OPTIONS') {
        set $cors = "cors_options";
      }
      if ($cors = "cors_options") {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        return 200;
      }
      if ($cors = "cors") {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        return 204;
      }

# 방법4 - 서버에 직접 설정하는 경우
# 아래는 go chi 핸들러 사용하는 예시
func CORSHandler(next http.Handler) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    // CORS(Cross-Origin Resource Sharing) 에러 방지 헤더 추가
    if r.Method == http.MethodOptions {
      // w.Header().Set("Access-Control-Allow-Origin", "*")
      // js fetch() credentials 옵션 사용시 와일드카드(*) 대신 요청 origin 으로 설정해야 한다.
      w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
      w.Header().Set("Access-Control-Allow-Credentials", "true")
      w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
      w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
      // options (preflight) 일때는 CORS 에러 방지 헤더 추가하여 바로 응답을 줘 클라가 다시 요청을 시도할 수 있도록 한다.
      return
    }
    w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
    w.Header().Set("Access-Control-Allow-Credentials", "true")
    w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
    next.ServeHTTP(w, r)
  }
  return http.HandlerFunc(fn)
}

# 주의사항
# 만약 k8s ingress 과 서버내 양쪽에서 모두 access-controll-allow 헤더들을 설정하면 중복 설정되고 CORS 정책에 위배돼 에러가 발생한다.
The 'Access-Control-Allow-Origin' header contains multiple values 'https://....', 
but only one is allowed. 
Have the server send the header with a valid value, or, 
if an opaque response serves your needs, 
set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

# 그래서 한쪽에서만 설정해줘야 한다.
# 참고로 curl 로 cors 확인(access-control-xxx 헤더)을 위해선 다음 옵션을 사용해야 한다.
curl -X GET "https://ysoftman.lemon.com/aaa/bbb?ccc=lemon" \
-H "accept: application/json" \
--header 'Origin: http://ysoftman.lemon.com' \
--header 'Access-Control-Request-Headers: Origin, Accept, Content-Type' \
--header 'Access-Control-Request-Method: GET'

drone ci 사용하기

# golang 으로 만들고 docker 환경으로 실행되는 drone ci 를 사용해보자.

# 우선 로컬에서 테스트하기 때문에 
# (reverse proxy 로 ngrok 도메인 -> localhost 로 터널링 해주는) ngrok 설치
brew install ngrok

# http://fe9b41d98488.ngrok.io -> http://localhost:80 포워딩을 위해 실행
# 참고 https (tls) 터널링은 유료 버전만 가능하고, 그외 동시접속 제한이 있다.
ngrok http 80

# github oauth 로 drone 앱(서버)를 등록
# github -> settings -> developers -> oauth apps 에서 신규 생성한다.
# homepage url : http://dd441e3e9f16.ngrok.io
# callback url : http://dd441e3e9f16.ngrok.io/login

# oauth app 생성후
# client -> DRONE_GITHUB_CLIENT_ID
# client secret -> DRONE_GITHUB_CLIENT_SECRET 로 사용한다.
# generate a new client secret(생성후 한번 볼 수 있어 복붙해둬야 한다) -> DRONE_GITHUB_CLIENT_SECRET 값으로 사용

# drone <-> runner 간 RPC 통신을 위한 secret 생성 -> DRONE_RPC_SECRET 값으로 사용
openssl rand -hex 16                         
d087c94b8155367b1982238df930c2f2

# drone 도커 이미지 다운로드
docker pull drone/drone:1

# 실행
# github enterprise 는 깃헙 서버 환경 변수 설정 필요
# --env=DRONE_GITHUB_SERVER=https://github.ysoftman.com \
# 볼륨 연결로 /data/database.sqlite -> 호스트의 /var/lib/drone/database.sqlite 로 저장
docker run \
  --volume=/var/lib/drone:/data \
  --env=DRONE_DATABASE_DRIVER=sqlite3 \
  --env=DRONE_DATABASE_DATASOURCE=/data/database.sqlite \
  --env=DRONE_GITHUB_CLIENT_ID=깃헙_클라이언트_id \
  --env=DRONE_GITHUB_CLIENT_SECRET=깃헙_클라이언트_secret \
  --env=DRONE_RPC_SECRET=d087c94b8155367b1982238df930c2f2 \
  --env=DRONE_SERVER_HOST=127.0.0.1 \
  --env=DRONE_SERVER_PROTO=http \
  --env=DRONE_WEBHOOK_ENDPOINT=http://dd441e3e9f16.ngrok.io/hook \
  --env=DRONE_WEBHOOK_SECRET=d087c94b8155367b1982238df930c2f2 \
  --env=DRONE_REPOSITORY_FILTER=ysoftman,bill \
  --publish=80:80 \
  --publish=443:443 \
  --restart=always \
  --detach=true \
  --name=drone \
  drone/drone:1

# 이제 다음 url 로 github authorize 하면 깃헙으로 로그인상태로 drone ci 를 사용할 수 있다.
http://dd441e3e9f16.ngrok.io

# DRONE_REPOSITORY_FILTER 으로 특정 저장소에만 drone과 연결 할 수 있다.
# 설정 변경 후 사용자별로 로그아웃 후 다시 로그인 해야 반영된다.

#####

# 상황에 맞는 여러 runner(러너)가 있다.
# docker(임시 컨테이너를 생성해 실행할때 사용) 러너 설치
docker pull drone/drone-runner-docker:1
docker run -d \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e DRONE_RPC_PROTO=http \
  -e DRONE_RPC_HOST=dd441e3e9f16.ngrok.io \
  -e DRONE_RPC_SECRET=d087c94b8155367b1982238df930c2f \
  -e DRONE_RUNNER_CAPACITY=2 \
  -e DRONE_RUNNER_NAME=${HOSTNAME} \
  -e DRONE_DEBUG=true \
  -e DRONE_TRACE=true \
  -p 3000:3000 \
  --restart always \
  --name runner \
  drone/drone-runner-docker:1

# exec(docker 와 같은 고립된 환경이 아닌 drone 서버 장비에서 쉘 환경에서 명령을 실행할 때 사용) 러너를 설치
curl --proxy "필요한 경우 프록시 설정" -L https://github.com/drone-runners/drone-runner-exec/releases/latest/download/drone_runner_exec_linux_amd64.tar.gz | tar zx
sudo install -t /usr/local/bin drone-runner-exec
mkdir -p /home/ysoftman/.drone-runner-exec
touch /home/ysoftman/.drone-runner-exec/config
cat > /etc/drone-runner-exec/config << eof
DRONE_RPC_PROTO=http
DRONE_RPC_HOST=dd441e3e9f16.ngrok.io
DRONE_RPC_SECRET=d087c94b8155367b1982238df930c2f
DRONE_LOG_FILE=/home/ysoftman/.drone-runner-exec/log.txt
eof
drone-runner-exec service install
drone-runner-exec service start

# drone-runner-exec 서비스 상태 확인
systemctl status drone-runner-exec.service

# docker 환경 변수 확인
docker inspect -f "{{.Config.Env}}" drone  | tr " " "\n"


# ssh(ssh 로 서버에 접속해 명령을 실행하는 용도) 러너 설치
docker pull drone/drone-runner-ssh
docker run -d \
  -e DRONE_RPC_PROTO=http \
  -e DRONE_RPC_HOST=dd441e3e9f16.ngrok.io \
  -e DRONE_RPC_SECRET=d087c94b8155367b1982238df930c2f \
  -p 3000:3000 \
  --restart always \
  --name runner \
  drone/drone-runner-ssh

# 루트 경로에 .drone.yml 를 작성한다.
# 파일명은 drone ci settings 에서 변경 가능하다.
---
kind: pipeline
type: exec
name: default

platform:
  os: linux
  arch: amd64

trigger:
  event:
    - push
steps:
  - name: 작업1
    commands:
      - echo hello world1
  - name: 작업2
    commands:
      - echo hello world2
  - name: 슬랙 알림
    # environment:
    #   noti_msg: "Branch: ${DRONE_BRANCH}\nAuthor: ${DRONE_COMMIT_AUTHOR}\nLink: ${DRONE_COMMIT_LINK}"
    #   build_status: "✅ build ${DRONE_BUILD_NUMBER} succeeded. Good job."
    commands:
      - echo "파이프 종료"
      - export HTTP_PROXY="http://프록시서버"
      - export noti_msg="Branch ${DRONE_BRANCH}\nAuthor ${DRONE_COMMIT_AUTHOR}\nLink ${DRONE_COMMIT_LINK}"
      - export build_status="✅ build ${DRONE_BUILD_NUMBER} succeeded. Good job."
      - echo $DRONE_BUILD_STATUS
      - if [[ $DRONE_BUILD_STATUS == "failure" ]]; then build_status="❌ build ${DRONE_BUILD_NUMBER} failed. Fix me please."; fi
      - |
        curl https://hooks.slack.com/services/..... -d "payload={\"channel\": \"#billtest\", \"text\": \"파이프 라인 시작\n$noti_msg\n$build_status\"}"
# ---
# kind: pipeline
# type: ssh
# name: default
# # 접속할 대상 서버
# server:
#   host: ysoftman-server.com
#   user: deploy
#   password:
#     from_secret: password
# steps:
#   - name: 작업1
#     commands:
#       - echo hello world1
#   - name: 작업2
#     commands:
#       - echo hello world2
# ---
# kind: pipeline
# type: docker
# name: slack notification
# # 사내환경등에서 외부 접속이 안되는 경우 프록시 환경 변수 설정
# environment:
#  HTTP_PROXY: "http://프록시주소"
#  HTTPS_PROXY: "http://프록시주소"
# steps:
#   - name: 슬랙 알림
#     # 사내환경등에서 외부 이미지 다운로드가 안되는 경우 알맞게 변경
#     image: plugins/slack
#     settings:
#       # https://my.slack.com/services/new/incoming-webhook 참고
#       webhook: https://hooks.slack.com/services/.....
#       channel: ysoftman-test
#       template: >
#         {{#success build.status}}
#           build {{build.number}} succeeded. Good job.
#         {{else}}
#           build {{build.number}} failed. Fix me please.
#         {{/success}}
#         Branch: {{ build.branch }}
#         Author: {{ build.author }}
#         Link: {{ build.link }}

# 이제 drone DRONE_WEBHOOK_ENDPOINT 변수로 설정된 url 을
# github 저장소 webhook url 을 추가해 push 되면 drone 에 알리도록 한다.
http://dd441e3e9f16.ngrok.io/hook

# 참고로 위의 HTTP_PROXY 와 같은 환경변수를 사용할때 
# ${HTTP_PROXY} 는 안되고 {} 를 제거한 $HTTP_PROXY 로 사용해야 한다.

#####

# drone cli 툴 사용 https://docs.drone.io/cli/install/
# 설치
brew install drone-cli

# aaaaabbbbb11111 는 drone > User Settings > token 로 확인
# 접속할 drone 서버 환경 변수 설정
export DRONE_SERVER=http://dd441e3e9f16.ngrok.io
export DRONE_TOKEN=aaaaabbbbb11111

# 접속 여부 확인
drone info

# .drone.yml 작업 수행(커밋 전에 미리 로컬에서 테스트할 수 있다.)
# drone exec 현재 type: docker 만 실행된다.
# .drone.yml 을 다음과 같이 만들고
kind: pipeline
type: docker
name: default

steps:
- name: build
  image: golang:1.13
  commands:
  - echo "aaa"

# 실행
# .drone.yml syntax 체크등에 활용할 수 있다.
drone exec

curl http3 지원 빌드

# http3 가 아직 curl 정식지원하지 않아 별도 빌드해보자
# 현재 ngtcp2, quiche 2종류의 라이브러를 사용해서 빌드할 수 있다.
# http3 옵션 사용 가능한 curl 빌드 스크립트
https://github.com/ysoftman/test_code/tree/develop/curl_with_http3

helm command

# helm 은 redis, jenkins, couchbase 등 널리 알려진 패키지(k8s에서는 리소스)을
# kubernetes(k8s) 환경에 쉽게 배포 운영해주는 일종의 패키지(리소스) 매니져다.
# helm 은 charts(k8s 리소스 생성을 위한 설정들 모아놓은 패키지)를 관리하는 방식으로 사용된다.
# kubectl 설치하고
wget https://storage.googleapis.com/kubernetes-release/release/v1.16.0/bin/linux/amd64/kubectl
chmod +x kubectl
sudo cp kubectl /usr/local/bin

# kubectl config 로 context 를 설정해야 한다.
kubectl config set-credentials xxx
kubectl config set-cluster xxx
kubectl config set-context xxx
kubectl config use-context xxx

# 설치 - mac
brew install kubernetes-helm

# 설치 - linux
# github 에 이미 빌드된 helm binary 사용한다.
# https://github.com/helm/helm/releases
# 참고로 helm 서버와 버전이 호환되는 helm client 를 사용해야 한다.
wget https://get.helm.sh/helm-v2.16.1-linux-amd64.tar.gz
tar zxvf helm-v2.16.1-linux-amd64.tar.gz
sudo cp linux-amd64 /usr/local/helm

# Error: incompatible versions 로 클라,서버 버전이 다른 경우
# helm 클라이언트를 서버 버전(2.14.1)으로 설치한다.
curl -L https://git.io/get_helm.sh | bash -s -- --version v2.14.1

# 초기화
# ~/.helm 을 셋팅한다.
helm init

# helm 으로 설치된 리스트
helm list -A

# 패키시 상태(Deployment, Service, Ingress...등)
helm status 이름

# helm chart 저장소 업데이트
helm update repo

# helm redis 설치
# 사용 가능한 redis chart 검색
helm search redis

# chart 상세보기
helm inspect stable/redis

# syntax 체크, -f value.yaml
helm lint stable/redis -f stable/values-dev.yaml

# 템플릿 렌더링 결과
helm template stable/redis -f stable/values-dev.yaml

# 설치 시뮬레이션, 디버그 메시지 출력
helm install stable/redis --name ysoftman-redis -n ysoftman-ns --dry-run --debug

# 설치(redis 클러스터 구성시 다음 명령 실행 후 출력되는 NOTES 부분 참고)
helm install stable/redis --name ysoftman-redis -n ysoftman-ns

# 업데이트
# upgrade 에서는 --history-max 로 헬름으로 배포된 히스토리 내역 최대 개수 제한 할 수있다. 기본(256개)
helm upgrade ysoftman-redis stable/redis -n ysoftman-ns --history-max 3

# values.yaml 의 특정값을 변경해서 업데이트
helm upgrade ysoftman-redis stable/redis -n ysoftman-ns --set ysoftman.key1=aaa,ysoftman.key2=bbb

# NOTES 부분은 status 로 다시 볼 수 있다.
helm status ysoftman-redis

# history 보기
helm history ysoftman-redis

# 롤백
helm rollback ysoftman-redis 1

# 삭제, aliases: uninstall, del, delete, un
helm delete ysoftman-redis

# 커스텀 chart 만들기
# 참고 https://docs.bitnami.com/kubernetes/how-to/create-your-first-helm-chart/
# 기본 디렉토리와 파일들을 생성해 준다.
# 참고로 Go template 엔진을 사용한다.
helm create mychart

# mychart 내의 파일을 수정후 상위 디렉토리로 이동후 커스텀 chart 로 설치할 수 있다.
helm install ./mychart --name ysoftman-app

# helm 으로 관리되는(설치된) 리소스 보기
# k9s > helm 리소스로도 볼 수 있다.
kubectl get all -A -l='app.kubernetes.io/managed-by=Helm'

access log - get null 400 에러

# tomcat 에러 중 다음과 같이 path 부분이 null 이 찍히는 경우가 있다.
# 참고로 로그는 apache httpd mod_log_config 를 사용한다.
https://httpd.apache.org/docs/2.4/ko/mod/mod_log_config.html

# /var/lib/tomcat8/conf/server.xml 에서 로그 포맷 설정
# %{Foobar}i - Foobar 헤더
# %h - 원격 호스트
# %l - 원격 identid가 제공한 로그인명, 기본값(-)
# %u - 원격 auth가 제공한 사용자명, 기본값(-)
# %t - 표준 영어 시간
# %r - 요청 첫번째 줄
# %s - 상태코드
# %b - http 헤더를 제외한 전송 바이트 수
# %h %l %u %t "%r" %s %b 포맷

# tomcat access log 확인
sudo tail -f /var/log/tomcat8/localhost_access_log.2019-01-31.txt

# 브라우저에서 '아이유' 요청하면 URL 인코딩 되고 다음과 같이 정상 동작(로깅)
127.0.0.1 - - [31/Jan/2019:18:15:30 +0900] "GET /%EC%95%84%EC%9D%B4%EC%9C%A0 HTTP/1.1" 404 1110

# curl 로 다음처럼 한글이 URL인코딩 없이 요청하는 경우 null 로 되고
# tomcat server.xml 의 protocol 기본 설정이 HTTP/1.1 로 돼있어
# HTTP/1.0 을 사용해도 로그에 HTTP/1.1 로 기록된다.
curl "http://localhost:8080/아이유" --http1.0 -s > /dev/null

# path 가 null 로 되어 처리할 수 없게 된다.
127.0.0.1 - - [31/Jan/2019:18:15:30 +0900] "GET null HTTP/1.1" 400 800

# tomcat 에러 로그를 확인을 위해 모든 로그를 확인해보자.
sudo vi /var/lib/tomcat8/conf/logging.properties
java.util.logging.ConsoleHandler.level = ALL

# tomcat 재시작
sudo service tomcat8 restart

# 에러 로그를 확인해보면
sudo tail -f /var/log/tomcat8/catalina.out

java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986

# 관련 내용을 구글링 해보니 tomcat 7.0.73, 8.0.39, 8.5.7 이상에서 발생하고
# http connector 에 relaxedQueryChars 를 설정하라고 한다.
https://stackoverflow.com/questions/41053653/tomcat-8-is-not-able-to-handle-get-request-with-in-query-parameters

# tomcat 버전 확인하면 8.5.30.0 이다.
sudo bash /usr/share/tomcat8/bin/version.sh

# 다음과 같이 특수문자를 처리해봤지만 한글 utf8코드는 처리 되지 않았다.
sudo vi /var/lib/tomcat8/conf/server.xml

<Connector port="8080" protocol="HTTP/1.1"
                connectionTimeout="20000"
                redirectPort="8443" relaxedQueryChars="[]|{}^&#x5c;&#x60;&quot;&lt;&gt;" />

# relaxedQueryChars 특수문자는 명시할 수 있지만 non-alphabet 범위의 문자코드는 어떻게 명시할 방법이 없어, client 가 URL 인코딩을 해줘야 한다.

nginx add_header overwrite 문제

# nginx 에서 커스텀 헤더를 추가하기 위해 add_header 를 사용한다.
# 그런데 현재 스코프외에 상위 스코프에서 설정한 헤더는 설정되지 않는다.
# 다음의 경우
nginx.conf
http {
    add_header aaa "bill";
    server {
        server_name ysoftman.server.com;
        add_header bbb "yoon";
    }
}

# 응답을 보면 bbb 헤더값만 설정되어 있다.
curl -I "http://ysoftman.server.com"
bbb: yoon


# add_header 설명(http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header)
# 현재 레벨의 add_header 가 없는 경우만 상위 레벨(스코프)의 add_header 값을 상속 받는다고 한다.

# aaa, bbb 헤더를 모두 설정하려면 최하위 스코프에 설정해야 한다.
nginx.conf
http {
    server {
        server_name ysoftman.server.com;
        add_header aaa "bill";
        add_header bbb "yoon";
    }
}

# 응답을 보면 aaa, bbb 헤더 모두 설정되어 있다.
curl -I "http://ysoftman.server.com"
aaa: bill
bbb: yoon

curl 유용한 옵션

# wget
# 예전에는 웹에 올라온 파일을 콘솔환경에서 다운받을때 wget 명령어를 주로 사용했다.
# 다음과 같이 다운받을 대상 url 만 명시하면 파일이 다운로드되는 형식이다.
wget http://ysoftman.com/ysoftman.tar.gz

# ysoftman.out 파일로 내용 저장
wget -O ysoftman.out http://ysoftman.com/ysoftman.html

# -O 뒤 - 주면 stdout 으로 다운받은 내용을 강제 출력한다.
wget -O - http://ysoftman.com/ysoftman.html

# curl
# 언제부터인가 wget 대신 curl 을 많이 사용하게 된다.
# 보통 curl 특정 특정 웹의 html 응답을 파악할때 사용한다.
curl http://www.google.com

# curl 로 파일을 받을때는 -O (대문자 o) 옵션을 주면 된다.
curl -O http://ysoftman.com/ysoftman.tar.gz

# -o 로 파일을 명시할 수 도 있다.
# -o- 로 -를 주면 stdout 으로 다운받은 내용을 강제 출력한다.
curl -o ysoftman.out http://ysoftman.com/ysoftman.tar.gz

# 만약 다운받은 파일이 예상했던 크기보다 작다면 -v 옵션으로 다운로드 과정을 살펴보자.
curl -Ov http://ysoftman.com/ysoftman.tar.gz

# HTTP/1.1 302 (redirect) 로 해당 파일을 redirect 하여 다운로드를 받아야한다면
# -L 옵션으로 redirect 를 따라가서 파일을 다운로드 받을 수 있다.
# 참고로 wget 은 별도의 옵션없이 redirect 처리가 된다.
curl -OLv http://ysoftman.com/ysoftman.tar.gz

# POST 방식으로 data 를 요청할때
# -X POST 방식(-d 가 있으면 생략가능)
# -d 데이터, 파일을 사용할때는 @파일명
# --trace-ascii /dev/stdout 표준 출력을 트레이스하여 데이터 내용을 확인할 수 있다.
curl -v -X POST http://ysoftman.com -d "ysoftman" --trace-ascii /dev/stdout

# 파일 업로드
# -F 멀티파트 폼데이터로 파일 전송
# zzz : form 필드에서 aaa.txt 데이터가 들어갈 위치(키)
# @전송할 파일명,
curl -X POST -F 'zzz=@./aaa.txt' http://ysoftman.com/upload

# -s, --silent 진행상태 에러메시지등 표시하지 않기
curl -s http://www.google.co.kr

# -i --include 헤더와 바디 보기
curl -i http://www.google.co.kr

# -I, --head 헤더만 보기
curl -I http://www.google.co.kr

# -I 는 기본적으로 HEAD 커맨드를 사용한다.
# GET 요청시에서의 헤더만 보기
curl -I -X GET http://www.google.co.kr

# -L 302 redirect 하여 응답헤더 보기
curl -IL http://www.google.com

# -D filename 헤더 덤프해서 보기(- 로 파일명을 안줄 수 있다)
curl -D - http://www.google.co.kr

# -H, --header 요청헤더 설정(헤더는 대소문자 구분하지 않는다.)
curl -I -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' -H 'Accept-Encoding: br' https://www.google.co.kr

# 응답 데이터 크기만 보기
curl -s http://www.google.co.kr -w '%{size_download}\n' -o /dev/null

# 응답 상태 코드만 보기
curl -s http://www.google.co.kr -w '%{http_code}\n' -o /dev/null

# elapsed(경과시간) 측정
curl http://www.google.co.kr -w "\n\nConnect time: %{time_connect} Time to first byte: %{time_starttransfer} Total time: %{time_total}"

# https + http2 요청
curl -sI http://www.google.co.kr --http2

# https + http1.1 요청
curl -sI http://www.google.co.kr --http1.1

ElasticSearch 과 Kibana 사용하기

ElasticSearch 는 Java 로 만든 Apache Lucene(루씬)을 기반으로한 분산 검색엔진이다.
데이터를 JSON 형식으로 저장하여 스키마 프리하며 확장이 용이하며 검색 기능이 꽤 강력하여 여러곳에서 사용되고 있다.
Kibana 는 ElasticSearch 를 쉽게 사용할 수 있도록 도와주는 일종의 시각화 도구이다.

##########

# ElasticSearch 설치
# 기본적으로 Java 로 만든 루씬을 사용하기 때문에 Java 실행 환경이 우선되어야 한다.
# JAVA_HOME 환경 변수가 등록되어 있는지 확인하고 없으면 JDK 를 설치하자.
# https://www.elastic.co/downloads/elasticsearch  다운로드 후 압축만 풀어 사용하면 된다.
# Elastic 시리즈의 다른 제품도 많은데 ElasticSearch 와 연동하기 쉽다.
# windows에서는 elasticsearch-2.3.3.zip 기준
# 커맨드라인으로 서버 실행하기
bin\elasticsearch.bat
# 커맨드라인으로 실행하면 다음과 같이 자바 프로세스가 수행된다.
"c:\Program Files\Java\jdk1.7.0_21\bin\java"   -Xms256m -Xmx1g -Djava.awt.headless=true -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -Dfile.encoding=UTF-8 -Djna.nosys=true  -Delasticsearch -Des-foreground=yes -Des.path.home="C:\elasticsearch-2.3.3" -cp "C:\elasticsearch-2.3.3/lib/elasticsearch-2.3.3.jar;C:\elasticsearch-2.3.3/ㅁlib/*" "org.elasticsearch.bootstrap.Elasticsearch" start
# 윈도우 서비스로 등록하기(삭제는 remove 옵션)
bin\service.bat install
# 서비스로 서버를 시작하면 다음과 같은 프로세스가 수행된다.
elasticsearch-service-x64.exe

# osx 에서는
brew install elasticsearch
brew services start elasticsearch

# centos 에서는(5.2.2 기준, java 필요시 yum install java)
curl -O "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.2.2.tar.gz"
tar zxvf elasticsearch-5.2.2.tar.gz
# restart
cat elasticsearch.pid | xargs kill
./elasticsearch-5.2.2/bin/elasticsearch -d -p elasticsearch.pid

# 설정 파일 수정
vi config\elasticsearch.yml
# osx 에서는 (brew info elasticsearch 로 config 경로 확인)
vi /usr/local/etc/elasticsearch/elasticsearch.yml
cluster.name: ysoftman-cluster1
network.host: 10.10.10.10
http.port: 9200

# 서버 확인
# 다음과 같이 접속해보면 json 형식으로 클러스터명과 버전을 확인할 수 있다.
http://10.10.10.10:9200/

# 플러그인 설치(centos 에서는 bin/elasticsearch-plugin 사용)
plugin.bat install --verbose analysis-icu
# elasticsearch 2.3.3 버전에서 아래 플러그인이 지원되지 않아 skip
#plugin.bat install --verbose Aconex/elasticsearch-head
#plugin.bat install --verbose lukas-vlcek/bigdesk

# 설치된 플러그인 리스트
plugin.bat list

##########

# Kibana 설치
# windows 에서는
https://www.elastic.co/downloads/kibana 다운로드 후 압축풀어 사용한다.

# 설정 파일
vi config/kibana.yml
# osx 에서는 (brew info kibana 로 config 경로 확인)
vi /usr/local/etc/kibana/kibana.yml
server.port: 5601
server.host: "10.10.10.11"
elasticsearch.url: "http://10.10.10.10:9200"

# 시작(ElasticSearch 디폴트 10.10.10.10:9200 로 연결)
bin\kibana.bat

# osx 에서는
brew install kibana
brew services start kibana

# centos 에서는(5.2.2 기준)
curl -O "https://artifacts.elastic.co/downloads/kibana/kibana-5.2.2-linux-x86_64.tar.gz"
tar zxvf kibana-5.2.2-linux-x86_64.tar.gz

# 혹시 열려 있는 kibana 프로세스 확인
ps -ef | grep '.*node/bin/node.*src/cli'

# restart(daemon 설치 포스트 참고)
daemon --name=kibana --pidfile="/home/ysoftman/kibana.pid" --stop
daemon --name=kibana --pidfile="/home/ysoftman/kibana.pid" --chdir="/home/ysoftman" --  "./kibana-5.2.2-linux-x86_64/bin/kibana"

# kibana 확인
http://10.10.10.11:5601/

# 추후 logstash 로 유입된 로그 데이터를 검색(Discover->검색명령)할 수 있다.
# elasticsearch 는
# DSL(Domain Specific Language)의 json 형태의 쿼리로 검색할 수 있고
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html

# lucene 쿼리로도 검색할 수 있다.
https://www.elastic.co/guide/en/kibana/current/lucene-query.html

# 예제
# AND, OR, NOT 은 대문자로 써야하고 각각은 && || ! 로 쓸 수 있다.
# http.response.code 필드가 존재하고, 값은 200,204,302가 아닌것들 검색
(_exists_:http.response.code) AND NOT(http.response.code:200) AND NOT(http.response.code:204) AND NOT(http.response.code:302)

##########

# 테스트 해보기
# curl 바이러리 다운로드(윈도우 기준)
https://bintray.com/artifact/download/vszakats/generic/curl-7.49.0-win32-mingw.7z

# 환경변수 추가(윈도우 기준)
PATH=%PATH%;C:\curl-7.49.0-win32-mingw\bin

# 문서 추가(생성)
# 리눅스는 옵션값에 quote(')를 사용하지만 윈도우는 doublequote(")를 사용해야 한다.
curl -X PUT http://10.10.10.10:9200/2016-05-27/ysoftman/doc1 -d "
{
"no\":123456,
"name\":\"ysoftman\",
"etc\":\"melong\"
}
"

# 문서 조회(curl 대신 브라우저 URL 로 요청해도 된다.)
curl -X GET http://10.10.10.10:9200/2016-05-27/ysoftman/doc1

# 문서 검색(curl 대신 브라우저 URL 로 요청해도 된다.)
curl -X GET http://10.10.10.10:9200/2016-05-27/ysoftman/_search?q=name:ysoftman

# 문서 삭제
curl -X DELETE http://10.10.10.10:9200/2016-05-27/ysoftman/doc1

# 참고
https://www.elastic.co/downloads/elasticsearch
https://www.elastic.co/guide/en/elasticsearch/plugins/2.3/plugin-management.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html
https://curl.haxx.se/docs/httpscripting.html#Modify_method_and_headers
http://d2.naver.com/helloworld/273788

JavaScript No Access-Control-Allow-Origin 에러 해결하기

# 보안상의 이유로 ajax(XMLHttpRequest) 로 요청하는 도메인과 요청받는 도메인이
# 다를 경우(크로스 도메인) 브라우저 개발 콘솔창에서 다음 에러메시지를 확인 할 수 있다.
"No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. "
# Firefox 로 보면
# CORS(cross origin resource sharing), same origin(동일 출처) 원칙에 따르면 현재 웹페이지의 리소스(스크립트, 이미지, 동영상등)의 출처가 현재 도메인과 다르면 안되지만 이를 가능하게 하는 메커니즘이다.

"교차 원본 요청 차단: 동일 출처 정책으로 인해 http://10.10.10.10/ysoftman 에 있는 원격 자원을 읽을 수 없습니다. 자원을 같은 도메인으로 이동시키거나 CORS를 활성화하여 해결할 수 있습니다."

# 예를 들어 로컬 디스크에서 개발중인 ajax내용이 있는 .html파일 크롬으로 실행시키면 발생할 수 있다.

# 내 PC 도메인은 file:///C:어쩌구저쩌구... 인데 실제 ajax 로 요청하는 호스트 도메인은 http://10.10.10.10 등으로 서로 다르기 때문이다.
# 서버단에서 해결방법
# 서버쪽에서 응답 헤더에 Access-Control-Allow-Origin 값을 모두 허용하도록 설정
# 서버 언어에 맞게 응답 헤더에 설정하도록 한다.
header("Access-Control-Allow-Origin", "*");
header("Access-Control-Allow-Origin: *");

# 클라(브러우저)에서 해결방법
# 크롬의 경우 현재 열려진 모든 크롬을 닫고 아래 옵션을 주어 실행
chrome --disable-web-security

# 아래 크롬 주소창로 크롬 실행시 어떤 옵션으로 실행되었는지 파악할 수 있다.
chrome://version

# IE의 경우
# 요청 후 나타나는 "차단된 콘텐츠 허용" -> 예 선택
# 외부 URL 요청이라면 웹브라우저가 preflight 요청을 수행한다.
# preflight 는 method=options 로 설정하고 해당 URL 로 미리 요청을 날려 요청이 허용(allow) 되는지 파악하는 과정이다.
# preflight 요청시에 다음과 같이 헤더 설정을 한다.
# POST 메소드를 사용할것이고 X-PINGOTHER 헤더가 포함될것이라고 알린다.
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER

# preflight (method=options) 요청후 서버 응답으로 서버에서 사용가능한 리소스, 메소드, 헤더등의 정보를 알려준다.
# http://foo.example 도메인에서만 허용된다.
# POST, GET, OPTIONS 메소드 허용된다.
# 86400 초 동안 preflight 응답을 캐싱
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

# preflight 응답이 다음과 같다면 모든 도메인에서 접근(요청)이 가능하다.
Access-Control-Allow-Origin: *

# 참고

Mac 패키지 관리자 brew 사용하기

Mac 에서 리눅스의 yum, apt-get 같은 기능을 해주는게 port 와 brew 라는 프로그램이다.
의존성이 있는 프로그램들을 알아서 설치해주니 필수로 설치하도록 하자.
port 보단 brew 를 더 많이 사용하는것 같다.

# brew
# 자세한 사용방법 :http://brew.sh/index_ko.html
# 설치하기(ruby 사용)
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

# brew 로 설치된 프로그램(패키지) 보기
# list 또는 ls
brew ls

# 패키지 path 보기
brew ls nmap

# 패키지 검색
brew search nmap

# brew 로 nmap 설치
brew install nmap

# 설치 후 과정만 다시 수행
brew postinstall nmap

# 설치된 패키지 중 old 버전 삭제
brew clean

# 패키지 삭제
brew remove nmap

# dead symlinks 인경우 제거
brew prune

# 패키지 정보(등록된 url 을 확인할 수 있다.)
brew info nmap

# curl 설치(http2 사용할 수 있도록)
# 2019년부터 --with-nghttp2 옵션 제거됐다.
# https://github.com/Homebrew/homebrew-core/issues/31510
# brew install curl --with-nghttp2
# http2 를 사용하려면 대신 curl-openssl 을 설치해야 한다.
brew install curl-openssl

# brew 로 설치된 프로그램은
# /usr/local/Cellar 에 다운로드되고
# /usr/local/bin 에 링크 파일을 생성한다.
# 원래 맥에서 기본 제공하는 /usr/bin/ 의 curl 같은 프로그램은
# brew 로 설치하면 /usr/local/bin 에 링크 파일이 생성되지 않는다.
# 이런 경우를 다음과 같이 keg-only 라고 한다.
"This formula is keg-only, which means it was not symlinked into /usr/local"

# 이렇게 설치시 자동으로 링크가 되지 않는 경우
# link --force 옵션으로 링크 파일을 강제 생성할 수 있다.
# /usr/local/bin/curl 링크 파일이 강제 생성
# 참고로 brew unlink 는 /usr/local/bin 링크 파일이 삭제한다.
brew link --force curl-openssl

# 그런데 macOS 에서 link --force 도 거부된다.
Warning: Refusing to link macOS-provided software: curl-openssl

# 대신 PATH 환경변수를 사용해야 한다.
export PATH=/usr/local/opt/curl-openssl/bin:$PATH

# 서비스(백그라운드) 작업시
brew services list
brew services start mysql@5.7
brew services stop mysql@5.7

C/C++ test

# loop

# 한글 utf-8 출력

# memset

# malloc() 과 calloc() 의 차이

# fopen의 text모드와 binary모드 차이

# STL int string 변환 

# switch 문안에서 변수선언과 동시에 초기화

# 한글 완성형 글자 출력하기

# bit field 테스트

# casting

# Windows 에서 32bit, 64bit 메모리 할당

# 정수형 상수 오버플로우

# 전위,후위 증(감) 연산

# libcurl test

# libjson test

# jsoncpp library 테스트

# linux RWLock test

# 한글 utf-8 출력

# time test

# string 스트림 사용시 첫번째 바이트가 \t \n \r 등 특수문자인 경우 빠지는 문제

# URL 인코딩/디코딩

# linux function test

# 정적 인스턴스와 동적 인스턴스의 차이

# C++ 미리 정의된 매크로

# 파일 헤더로 Unicode(utf-16) 와 utf-8 구분하기

# float, double 과 같은 부동소수점 타입 연산시 유의 사항


# random test

# 실인수(argument) 와 가인수(parameter) 구분

# strtok() 함수를 이용한 연속 토큰 추출

# Linux fork() 를 이용한 데몬 프로그램

# swap 방법들