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

nginx https websocket newline error

# 현상
# k8s pod 접근시 nginx https 를 경유 exec 로 접속 후 엔터를 치면 다음 처럼 prompt가 보이고
root@ysoftman-123:/aaa#
(커서) 여기서 멈춰있다., 엔터를 치면 다시 prompt가 뜨고 다시 똑같이 prompt가 보이고 커서 다음줄에 위치하는 문제가 있다.
nginx http 를 통하면 문제가 없다.

# k8s client python 를 사용 중이고
# websocket 이 연결되어 있는 동안 stdout, sterr 를 받아 출력하도록 했다.
while websocket_client.is_open():
    websocket_client.update(timeout=1)
    if websocket_client.peek_stdout():
        print(websocket_client.read_stdout(), file=sys.stdout, flush=True, end='')
    if websocket_client.peek_stderr():
        print(websocket_client.read_stderr(), file=sys.stderr, flush=True, end='')

# 테스트 환경
# nginx 가 --with-debug 로 빌드되었는지 확인
nginx -V | grep -i with-debug

# ngnix.config 에러 로그에 debug 레벨을 추가하자.
error_log  /usr/local/var/log/nginx/error.log debug;

# nginx 를 리로딩하기
sudo nginx -s reload

# 디버깅 로깅을 보면
tail -F /usr/local/var/log/nginx/error.log

# pod 접속 후 키를 입력할때마다 nginx debug 다음과 같은 로그가 찍한다.
# (엔터) 친 경우 prompt 가 출력되어야 한다.

# newline 에 커서가 가있지만 prompt 가 안뜨는 경우
2023/05/10 13:24:33 [debug] 40385#0: *58 http upstream process upgraded, fu:1
2023/05/10 13:24:33 [debug] 40385#0: *58 recv: eof:0, avail:150, err:0
2023/05/10 13:24:33 [debug] 40385#0: *58 recv: fd:15 150 of 4096
2023/05/10 13:24:33 [debug] 40385#0: *58 SSL to write: 150
2023/05/10 13:24:33 [debug] 40385#0: *58 SSL_write: 150
2023/05/10 13:24:33 [debug] 40385#0: *58 event timer: 15, old: 17342356, new: 17342362
2023/05/10 13:24:33 [debug] 40385#0: timer delta: 6
2023/05/10 13:24:33 [debug] 40385#0: worker cycle

# newline 에 prompt 정상적으로 뜨는 경우도 가끔 발생했다.
2023/05/10 13:24:50 [debug] 40385#0: *58 http upstream process upgraded, fu:1
2023/05/10 13:24:50 [debug] 40385#0: *58 recv: eof:0, avail:147, err:0
2023/05/10 13:24:50 [debug] 40385#0: *58 recv: fd:15 147 of 4096
2023/05/10 13:24:50 [debug] 40385#0: *58 SSL to write: 147
2023/05/10 13:24:50 [debug] 40385#0: *58 SSL_write: 147
2023/05/10 13:24:50 [debug] 40385#0: *58 event timer: 15, old: 17359466, new: 17359540
2023/05/10 13:24:50 [debug] 40385#0: timer delta: 2

# http 로 연결한 경우 recv 150 인데도, prompt 가 잘뜬다.
2023/05/11 13:44:27 [debug] 41253#0: *48 http upstream process upgraded, fu:1
2023/05/11 13:44:27 [debug] 41253#0: *48 recv: eof:0, avail:150, err:0
2023/05/11 13:44:27 [debug] 41253#0: *48 recv: fd:13 150 of 4096
2023/05/11 13:44:27 [debug] 41253#0: *48 send: fd:12 150 of 150
2023/05/11 13:44:27 [debug] 41253#0: *48 event timer: 13, old: 104937207, new: 104937220
2023/05/11 13:44:27 [debug] 41253#0: timer delta: 12

# 그냥 엔터만 친 경우 150(비정상), 147(정상) 의 데이터 크기 차이를 보인다.
# http 에서도 150, 147 둘다 나오는데, 둘다 prompt 가 정상적으로 출력된다.
# 데이터가 프롬프트 길이 뒤에 값이 추가되는데 https 연결상에서는 이것이 newline 으로 취급되는것으로 보인다.
# update() -> print(data) 로 추가해서
# 엔터를 쳤을대 받는 데이터를 출력해보면 
150 -> 비정상인 경우 b'\x01\r\n'
147 -> 정상인 경우 b'\x01\r\n\x1b]0;프롬프트 스트링'

# 0x1(SOH, start of heading)
# \r\n(CR:carriage-return, LF:linefeed) newline
# 0x1b]0;로 x1b(escape) 가 포함되어 있음
# http 에서는 b'\x01\r\n' 인 경우에도 b'\x01\x1b]0; 로 시작하는 prompt 응답이 온다.

# websocket python 트레이싱 해보면
enableTrace(True)

# update() -> polling 을 해서 recv 데이터를 보여주는데 여기에 b'\x01\r\n' 만 있고 prompt 데이터는 나오지 않는다.
# nginx 는 150(byte) 으로 응답했다고 하는것 같은데, ws client 는 3바이트의 newline(b'\x01\r\n')만 받고 
# 그 뒤로는 recv 데이터를 받은 것이 없다고 트레이싱 된다.
# (http 에서는 newline 이후에도 prompt 데이터를 받았다고 트레이싱 된다.)

# ws_client 소스에서 update() 부분에서 응답 패킷을 받는데
# r 체크 조건을 제거하면 https 상태에서도 prompt 데이터를 받는다.
# if r:
op_code, frame = self.sock.recv_data_frame(True)

# 결국 websocket client 데이터를 받는 r(polling)이 제대로 되지않는게 문제로 보인다.
# update() 에서 polling 없이 sock.recv_data_frame(True) 를 받을 수 있도록 하고
# peek_channel() 에서는 self.updat(timeout=timout)을 제거하니
# http, https 둘다 newline 후 prompt 가 잘 표시되었다.

wsgidav(webdav) session hang

# k8s ingress > webdav(https://github.com/mar10/wsgidav) pod 환경으로 사용 중 수천개의 파일을 업로그 하는경우 같이 다수의 요청이 발생하면 hangup(응답없음) 현상이 발생한다.

# webdav 테스트이미지(htop, netstat 등 추가)를 새로 만들고 적용해서 모니터링해보자.
# webdav 설정 thread=10 개라 10개의 파이썬 프로세스에서 10개의 tcp session 까지 처리할 수 있는 상태다.
# 도메인으로 요청시 세션(tcp ESTABLISHED)이 늘어났지만 세션 종료는 안돼 새 연결 요청을 받지 못하는것으로 보인다.

# webdav 용 클라이언트 코드에 ysoftman_test:8080 로 하드코딩하고 /etc/host 에 아래와 같이 호스트를 추가하고
10.10.10.10  ysoftman_test

# 아래와 같이 요청을 계속 날리고 webdav 서버 container netstat -nat 로 보면 세션이 늘어나지 않고 잘 처리됐다.
watch -n 0.1 "httpstat http://ysoftamn:abc123@ysoftman_test"

# 위와 같이 ingress(Ingress NGINX Controller)를 경유하지 않으면 된다.
# 그럼 nginx controller 에서 뭔 keepalive 와 같은 일정 시간 연결을 유지하는것 때문이지 않을까?
# controller 의 confimap 에 keepalive 를 사용하지 않도록 하는 설정이 있다.
# ingress nginx controller configmap > data 에 다음 설정을 추가하면 세션 keepalive 를 사용하지 않는다.(요청 완료된 세션 종료)
# 하지만 configmap 설정이라 ingress 를 사용하는 모드 서비스에 영향을 주어 테스트만하고 실제 적용은 하지 않았다. 
upstream-keepalive-connections: "0"

# 참고로 http 1.0 에서는 요청 처리후 연결을 끊지만 http 1.1 부터는 keepalive 가 기본으로 tcp 연결을 일정시간동안 유지한다.
In HTTP/1.1, persistence is the default, and the header is no longer needed (but it is often added as a defensive measure against cases requiring a fallback to HTTP/1.0).

# webdav 에서도 0.4.0 b1 부터 http1.1 을 사용하고 있었다.
0.4.0.b1
- Using HTTP/1.1 with keep-alive (Stéphane Klein)

# 사실 keepalive 는 파일 전송과 같이 지속적인 연결이 필요한 경우 효율성이 좋지만 새로운 연결(요청)은 세션이 부족할 경우 대기하는게 문제다.
# 예를 들어 수천개의 파일을 동시에 올릴때 thread=10개라면 10개는 처리되지만 이후 요청은 10개의 keepalive 가 종료(세션종료)때까지 대기해야 하는 문제가 있다.
# wsgidav 은 numthreads 개수 만큼 파이썬 프로세스가 떠서 요청을 받는 구조인데 numthreads=1000 처럼 늘리면 CPU 사용량은 늘어나겠지만 더 많은 새로운 요청을 받아 줄 수 있다.
# numthreads=1000 로 이미지를 새로 만들고 httpstat 테스트하면 세션수가 일정한 범위내에서 더 늘어나지 않는다.

# 실제 클리이언트를 사용해서 테스트
# 클라이언트는 10개의 worker thread 로 수만의 파일을 업로드 하고 있고 
# 서버의 가능 세션수가 1000 개인 상태에서 업로드를 수행하면
# 앞선 완료된 일정 시간 후 세션 클로즈 되고 다시 사용할 세션수 있는 세션수가 늘어나기 때문에 세션수가 일정하게 유지하면서 업로드를 할 수 있었다.
# 그리고 pod cpu 리소스는 최소 2000m 이상 잡아줘야 쓰레드를 충분히 사용하는것 같다.

# 요것도 원인 찾느라 힘들어서 개비스콘 짤 생생~ㅋ

k8s ingress nginx 413

# ingress 설정후 http put 으로 10GB 파일을 업로드 하는데 413 Payload Too Large 에러가 발생했다.
# 원인은 ingress-nginx > container > client_max_body_size 100m 설정 때문이고
# 다음과 같이 ingress 에 proxy-body-size 값을 크게 주면 된다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ysoftman-ingress
  namespace: ysoftman
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/proxy-body-size: "100g"
    # 0 이면 clinet body size 를 체크하지 않는다.
    # nginx.ingress.kubernetes.io/proxy-body-size: "0"

# 참고로 nginx unit(단위)는 k, m, g 등의 suffix 를 사용한다.

# 참고로 request body 크기가 client_body_buffer_size(https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#client-body-buffer-size) 보다 크면 다음과 같은 위치에 파일을 쓴다고 한다.
a client request body is buffered to a temporary file /var/lib/nginx/body/0000000001

# 위치는 client_body_temp_path(https://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_temp_path)설정에 따라 변경될 수 있음

fastapi 422 error

# fastapi(web framework for building APIs with Python 3.6+) 서버에 요청시 422 에러 발생 해결하기

# 다음과 같은 요청시
curl 'http://localhost:8000/path/lemon/ysoftman' \
  -H 'Origin: http://localhost:8000' \
  -H 'X-User-Token: abc123' \
  --data-raw '{"name":"ysoftman","desc":"test","spec":{"val1":100,"val2":"asdf"}}'

# 응답으로 422(Unprocessable Entity)가 발생하며 다음과 같은 응답 메시지를 받는 경우가 있다.
{"detail":[{"loc":["query","data"],"msg":"field required","type":"value_error.missing"}]}%

# fastapi route 함수에서 파라메터 타입이 잘못된거나 빠지면 발생한다.
# BaseModel 을 이용해 파라메터 타입을 명시하면 된다.
class SpecObject(BaseModel):
    val1: int
    val2: str

class YsoftmanData(BaseModel):
    name: str
    desc: str
    spec: SpecObject

@router.post("/path/{val1}/ysoftman")
def post_ysoftman(val1: str, data: YsoftmanData, x_user_token: str = Header(default=None)):

# 참고

http cookie 설정 유의사항

# golang http.SetCookie 사용시
# 로컬에서 테스트를 위해 도메인:1234 로 쿠키 저장을 하려고 하면
# :포트를 명시해서 다음과 같은 에러가 발생하지만 저장은 된다.
# 하지만 에러가 표시를 없애기 위해 :포트는 명시하지 않는것이 좋다.
net/http: invalid Cookie.Domain "ysoftman.testdomain.com:1234"; dropping domain attribute

# 참고
# 쿠키 설정 참고 사항
https://tools.ietf.org/html/rfc6265#section-4.1.1
# IP 는 안되고 스트링 소문자로 저장되며
5.1.3.  Domain Matching

# . 으로 시작되는 도메인은 .을 제외시킨다.
5.2.3.  The Domain Attribute
생략
Let cookie-domain be the attribute-value without the leading %x2E
      (".") character.

# 쿠키는 같은 도메인에서 port 로 구분 할 수 없다.
8.5.  Weak Confidentiality
   Cookies do not provide isolation by port.

jwt 사용시 추가 보안

# JWT(Json Web Token) 을 잠깐 알아보면,
# . 구분자로 header.payload.signature 로 구분되고
# payload 에 필요한 정보를 담아 클라<->서버간 주고 받게 된다.
# jwt 토큰 예시
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwaG9uZSI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoieXNvZnRtYW4ifQ.IBNMyC-NPJXL0pk9w7Dmq5HZz5o-3OUquGUBlXyWCvk

# 위 jwt 토큰은 다음과 같이 누구나 복호화 (https://jwt.io/)할 수 있다.
# 복화된 내용을 수정하면 바로 암호화된 jwt 도 생성할 수 있다.
# header: algorithm & token type
{
  "alg": "HS256",
  "typ": "JWT"
}

# payload:data
{
  "phone": "1234567890",
  "name": "ysoftman"
}

# verify signature
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
) secret base64 encoded

# 보통 위와 같이 JWT 토큰에 사용자 식별정보(id)를 서버에 전송하면
# 서버에서는 해당 id 에 대한 권한을 주는 흐름이다.

# 기본으로 보안되는 것
# 서버는 jwt 를 생성시 미리 정한 sigkey(secret) 을 넣어 서버에서만 사용되는 jwt 를 만들 수 있다. 때문에 클라가 payload 등의 내용을 임의로 변경해서 새로운 jwt 만들어 서버에 전송해도 서버에선 유효하지 않은 jwt 로 무시하게 된다. 즉 서버가 발행하지 않는 변조된 jwt 는 서버에 사용할 수 없다.

# 추가 보안으로 고려해야 하는 것
# 1. 해커가 jwt 토큰을 갈취해서 요청 할 수 있다.
# -> 서버에서 쿠키 생성시
# secure;(쿠키가 전송되지만 https 에서만 쿠키가 설정된다.)
# httponly;(javascript 등의 브라우저가 아닌 곳에서 접근할 수 없다.)
# 옵션을 설정할 필요가 있다.
# 참고 https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies -> Restrict access to cookies

# 2. jwt 토큰 갈취 위험 빈도를 낮추기 위해
# access token(실제 인증을 위한 짧긴 1시간 정도의 만료 기간을 가진 토큰),
# refresh token(access token 이 만료 됐을때 새 access token 발급을 위한 긴 2주 정도의 만료 기간을 가진 토큰) 2개를 이용한다.
# 로그인시 2개의 토큰을 생성하고 서버는 사용자의 refresh token 을
# db 에 저장해 두고 클라에게 응답하면 클라는 refresh token 은 저장해두고
# access token 만 사용하다가 서버로 부터 access token 이 만료됐다고 했을때
# access token + refresh token 을 요청해서
# access token + refresh token 을 갱신받는다.
# 이 방식은 매 요청에 사용되는 access token 이 갈취 당해도 비교적 짧은 시간만 유효함으로 보다 안전하고 한다.

# 3. payload 내용에 사용자id 등의 개인 정보가 노출될 수 있다.
# -> jwt payload 도 aes256 같은 대칭키로 암호화한다.

golang https 인증서 사용

# golang 에서는 다음과 같이 인증서 및 키 파일을 이용해 https 를 사용 한다.
go func() {
  if err := http.ListenAndServeTLS(":443", "server.cert", "server.key", myhandler); err != nil {
  log.Fatal(err)
  }
}()

# 키 파일에 암호가 적용된 경우 다음과 같이 파싱 에러가 발생한다.
tls: failed to parse private key


# 따라서 다음과 같이 키파일에서 암호를 제거한 키파일을 만들어 사용해야 한다.
openssl rsa -in server.key -out nopassword_server.key
Enter pass phrase for key.key: 암호입력
writing RSA key

chrome ERR_CONNECTION_RESET

rust 로 간단한 html 응답을 주는 웹 서버를 만들어 테스트하기 위해 chrome 에서 다음과 같이 입력하면 화면에 아무것도 나타나지 않는다.
chrome secret 모드에서도 안된다.
https://localhost:9090

같은 주소에 대해서 firefox 는 표시된다.
curl http://localhost:9090/ 요청도 html 응답을 받고 있다.
rust 서버에서 http request/response 출력해보면 응답도 잘 주고 있다.

chrome 79.0.3945.117 버전을 사용하는데,
chrome 개발툴 -> network 요청결과 status fail 이고,
chrome 개발툴 -> console 에는 다음과 같은 에러가 보인다.
GET http://localhost:9090/ net::ERR_CONNECTION_RESET 200 (OK)
맨 오른쪽 :9090/:1 링크를 클릭하면 그제야 html 화면이 보인다.

data URL 사용하기

# 모던 브라우저들은 data 프로토콜(data: 로 시작하는 URL)을 사용해
# 이미지같은 데이터를 로링할 수 있다.
# URL 에 데이터가 명시되어 있어 네트워크 요청이 필요없지만
# 32kb 제한으로 이미지 썸네일등의 작은 데이터에 로딩에 사용한다.
# 참고 https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs

# 형식은 다음과 같다.
# data: 데이터 프로토콜
# image/jpeg 미디어 타입으로 MIMETYPE 으로 명시한다.
# ;base64 인코딩 타입
# ,/aaaa 인코딩된 데이터값
data:image/jpeg;base64,/aaaaa

# 실제 이미지 데이터를 가진 data URL, chrome 으로 열 수 있다.
data:image/jpeg;base64,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

golang MaxIdleConnsPerHost 조정하기

# golang 기본 클라이언트 transport 의 기본 설정은
# 한개의 호스트에 Idle Connections(time_wait)를 2개로 제한되어 있다.
# 이를 변경해서 time_wait 를 줄일 수 있다.
# 참고
http://tleyden.github.io/blog/2016/11/21/tuning-the-go-http-client-library-for-load-testing/

# 테스트 내용
https://github.com/ysoftman/test_code/blob/develop/golang/max_idle_conns_per_host/max_idle_conns_per_host.go

간단한 http server 프로그램

# 테스트를 위해 http 요청을 처리할 수 있는 간단한 http server 프로그램들
# node.js 기반 http-server
# 설치
npm install http-server -g

# 현재 디렉토리의 파일을 서빙하는 http server 실행
# http://127.0.0.1:8080 로 접속
http-server ./

# python 기반 simple-http
# 현재 디렉토리의 파일을 서빙하는 http server 실행
# http://127.0.0.1:9999 로 접속
python -m SimpleHTTPServer 9999

# python3 에선 http.server 를 사용한다.
python3 -m http.server 9999

http 204 응답 브라우저 처리 방식

# http 응답이 다음과 같이 204 인 경우 브라우저 화면에 변화가 없다.
# 204 : 클라이언의 요청은 성공이지만 서버가 추가적으로 응답줘야할 내용이 없는 경우
status code: 204 No Content

# 찾아보니 204 응답을 받은 클라이언트는 현재 페이지를 벗어나지 않는다고 한다.
# 그래서 서버에서 204 응답으로 바디 내용 비운상태로 주더라도 브라우저는 
# 이전 페이지(204 요청 전)를 보여주고 있어
# 마치 204 요청에 이상한 응답을 주것으로 착각할 수 있다.  

# 실제 curl 로 확인하면 다음과 같이 내용이 없이 응답을 주는것을 확인 할 수 있다.

* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /ysoftman/test HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.57.0
> Accept: */*
>
< HTTP/1.1 204 No Content
< Content-Type: application/json; charset=UTF-8
< X-Content-Type-Options: nosniff
< X-Request-Id: d6e8e682-86f5-45d6-91d5-67a1c26efd38
< Date: Wed, 01 Aug 2018 06:13:50 GMT
<
* Connection #0 to host localhost left intact

# 204 상태 코드 설명 참고

Content Security Policy

# Content Security Policy(csp,컨텐츠 보안 정책)는
# 웹으로 서비스되는 도메인의 컨텐츠들은 같은 도메인을 명시하고 있어야(동일 출처의 원칙)
# 하는데 그렇지 않은 경우 Cross Site Scripting(XSS, 교사 사이트 스크립트)
# 공격에 취약할 수 있어 이를 방지하고자 적용되는 보안정책이다.
# 웹서버는 응답헤더에 다음과 같이 설정해주면 클라(브라우저)는 출처가
# self(현재출처) 나 https://apis.google.com 인 경우만 스크립트를 수행하게 된다.

Content-Security-Policy: script-src 'self' https://apis.google.com

# script-src 외에도 base-uri, img-src, midea-src, font-src ... 등
# 다양한 리소스 지시자를 사용할 수 있다.
# (각 리소스 지시자를 명시하지 않으면 디폴트로 모두 허용)

# default-src 로 -src 로 끝나는 지시자들의 디폴트 값을 설정하고
# report-uri 로 csp 위반 사항을 설정한 서버로 보고할 수 있다.
# 보고시 전송방식은 post 이고 내용은 json 형식으로 전달된다.

Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

# csp 를 적용하지 않고 위반되는 상황이 어떤것들이 있는지 파악(보고)만 하는 형태로
# 다음과 같은 헤더값을 사용할 수 도 있다.

Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

# 참고 및 세부설명
https://developers.google.com/web/fundamentals/security/csp/?hl=ko

http cookie expires syntax

http 응답에 쿠키설정(Set-Cookie)에 expires 를 어떻게 표기해야 할까?

모질라와 위키피디아 에는
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date
https://en.wikipedia.org/wiki/HTTP_cookie

Expires=Wed, 21 Oct 2015 07:28:00 GMT;
Expires=Tue, 15 Jan 2013 21:47:38 GMT;

와 같다. 그런데

https://www.ietf.org/rfc/rfc2109.txt -> Expires and Max-Age 을 보면

Wdy, DD-Mon-YY HH:MM:SS GMT
Wdy -> day of week
와 같이 요일 표기도 다르고, 일-월-년 에 dash 가 들어가 있다.

위키피디아에 보면 RFC2109(1997년)는 RFC2965(2000년)로 대체(superseded) 되었는데 RFC2965 에서 Set-Cookie2 만 추가되었고 이것도 거의 사용되 않아 deprecated 되었다.

암튼 2109, 2965 는 예전방식(Obsolete)이고,
최신 RFC6265(https://tools.ietf.org/html/rfc6265 , 2011년) 에 보면 다음과 같이 dash 가 빠져 있다.

Set-Cookie: lang=en-US; Expires=Wed, 09 Jun 2021 10:18:14 GMT


chrome cache data 보기

# 다음 url 로 크롬에서 캐시되고 있는 데이터를 볼 수 있다.
chrome://cache/

# 캐시 URL 링크를 클릭하면 다음과 같은 hex 덤프를 볼 수 있다.

# 위 hex dump 내용을 txt 파일로 만들고 다음 명령으로 html 으로 복구 할 수 있다.
# xxd -r : hdexdump revert
xxd -r chrome-cache.txt  >  chrome-cache.html

http 요청 흐름 보기

# httpstat 툴은 http 요청 처리 과정을 보여준다.
# 설치
pip install httpstat

# 실행결과 예시


# 참고
# (python 버전) https://github.com/reorx/httpstat

chrome http 301 응답 캐시 삭제

chrome(크롬) 에서 해당 사이트 요청에 301(permanent redirect) 응답을 받는 경우 캐시하여 계속 리다이렉트 하게된다.

301 은 영구적으로 사이트가 이동 되었다는 의미로 사용 되기 때문에 한번 301 응답을 받으면 클라이언트(브라우저는) 해당 사이트의 리다이렉트 값을 캐싱하여 웹서버 확인 없이 계속 옮겨진 사이트로 리다이렉트 한다.

웹서버는 영구적으로 사이트가 옮겨 졌다면 301 응답을 주면 되지만 임시로 옮겨 졌다면
302 (temporarily moved)를 사용하여 클라이언트가 계속 웹서버에 확인할 수 있도록 해야 한다.

크롬에서 아래 설정에서 301 응답 캐시를 지워주면 다시 해당사이트에 요청을 하여 확인 할 수 있다.

chrome Settings(설정) -> Show advanced settings(고급) ->  Privacy(개인정보 및 보안) -> Click Clear browsing data (인터넷 사용기록 삭제) -> Cached images and files (캐시된 이미지 또는 파일) 체크 후 삭제

웹서비스 robot.txt human.txt

웹서비스에서 web crawler 같은 로봇들의 활동을 제어하는 방안으로 robot.txt 을 루르 경로에 만들어 두고 사용한다.(권고 사항으로 강제되지는 않는다)

robot.txt 과 같은 방법으로 웹서비스에 기여한 사람들을 human.txt 파일로 만들어 두기도 한다.
예)

http2 에서의 keep-alive

http1.1 의 경우
request 헤더에 다음과 같이 "keep-alive" 를 명시하여 웹서버에 요청하면
Connection:keep-alive

웹서버가 keep-alive 기능을 지원한다면 response 헤더에 다음과 같이 설정된다.
Connection:Keep-Alive

그런데 http2 의 경우 connection 관련 헤더 필드는 따로 명시하지 않는다고 한다.
http://httpwg.org/specs/rfc7540.html#n-connection-specific-header-fields

공백 urlencoding

URL 인코딩시 공백(space)는 + 또는 %20 로 변환된다.
golang, python, java에서 urlencoding 하거나 구글 검색시 다음과 같이 1 과 2 사이 공백을 두었을 때
1 2
"1+2" 로 인코딩되고 있다.
공백문자는 +(plus) 기호로 변환하고 Non-alphanumeric 경우 %HH(헥사)로 변환된다고 한다.
%20 은 ASCII 에서 공백이 32(10진수) -> 20(16진수)로 Non-alphanumeric 을 %HH 로 표기룰을 적용하면 맞지만 그전에 공백은 + 로 변경하는 룰이 우선함으로 + 를 사용하는게 더 맞는것으로 보인다. + 문맥 구분도 잘 될 수 있어 보기도 편하다.