Young & Rich

 

안녕하세요.

 

오늘은 오랜만에 python 관련 포스팅을 해보도록 하겠습니다.

 

HTTP REST API 를 python 의 requests 모듈을 이용해서 많이 사용하실 겁니다.

 

python request 모듈을 이용한 HTTP 내용을 포스팅했던 적이 있었습니다.

https://yys630.tistory.com/38

 

파이썬 HTTP, Get, POST API, HTTP API

Python Request 모듈을 사용하여, 간단하게 HTTP API 를 사용할 수 있습니다. requests 모듈을 사용하여 간단하게 get, post 동작을 수행할 수 있습니다. HTTP Protocol 로 API 요청할 때 기본적으로 필요한 정보..

yys630.tistory.com

 

저도 일반적으로 requests API를 사용하긴 하지만,

keep-alive로 Session을 유지하면서 data 를 계속 send 하거나 recv 하는 경우에는

requests 모듈이 blocking 으로 동작하기 때문에 data 를 계속 send 하거나 recv 하는데 어려움이 있었습니다.

 

다른 모듈들을 찾아보다가

"그래서 직접 raw socket 으로 짜는 것이 맘 편하겠어"

라고 생각하여 직접 짜게 되었습니다.

 

import socket

evt_data = '--eventBoundary' + '\r\n\r\n' + 'Event:Birthday' + '\r\n' + '--eventBoundary' + '\r\n\r\n'

req_header = "POST {0} HTTP/1.1\r\nHost: 192.168.10.3:80\r\nUser-Agent: CUSTOM PYTHON\r\nContent-Type: multipart/mixed; boundary=eventBoundary\r\nContent-Length: {1}\r\n\r\n"
header = req_header.format(url, len(data))
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)   # Create socket
s.connect(("192.168.10.3",80))                   # Connect

data = header+evt_data			# Header + Body
s.send(data.encode('UTF-8'))

 

POST 방식으로 Body 에 Data(Content) 를 실어서 send 하는 방식입니다.

 

이렇게 하면 Blocking 없이 계속 Data 를 Send 할 수 있습니다.

 

특히 multipart 방식으로 Data 를 Send 하는 경우에 유용합니다.

위의 예제에서 '--eventBoundary' 를 기준으로(Transaction) 계속 data 를 send 할 수 있습니다.

 

근데 이렇게 짜면 한가지 걸리는 것이 있습니다.

 

그렇다면 인증을 어떻게 하나요....??

 

"기존 requests 에서는 인증을 다 해주는데 이건 Digest 인증을 직접 raw string 으로 만들어줘야하잖아요..."

 

라는 문제가 있습니다.

 

그래서 직접 모두 구현하기 힘드니깐 조합해서 써야지가 결론이였습니다.

 

import urllib3

from requests.utils import parse_dict_header, parse_list_header

import hashlib

from urllib.parse import urlparse

import os

 

401 로 recv 된 data 에서 필요한 realm, nonce, algorithm 을 파싱하여,

(requests.utils 에 존재합니다.)

 

Digest 인증해주는 String 영역을 만들어주는 부분을 Custom 하게 만들었습니다.

 

reuqests 모듈 내부에서도 urllib3 과 hashlib 을 이용하여 digest auth string 을 만들어주게 됩니다.

 

공개되어 있는 requests 모듈의 소스를 참고해서 구현하셔도 되고,

스펙을 직접 보면서 구현하셔도 됩니다.

 

import urllib3
from requests.utils import parse_dict_header, parse_list_header
import hashlib
from urllib.parse import urlparse
import os

req_header = "GET {0} HTTP/1.1\r\nHost: 192.168.10.3:80\r\nUser-Agent: CUSTOM PYTHON\r\n\r\n"
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)   # Create socket
s.connect(("192.168.10.3",80))                   # Connect it
header = req_header.format(url)
data = header
s.send(data.encode('UTF-8'))
r = s.recv(1024)
response = r.decode('utf-8')

# Header Parsing
resp = response.split('\r\n')

# auth 정보 Parsing
for get_auth in resp:
	if 'WWW-Authenticate' in get_auth :
		auth_info = get_auth

# Dictionary 로 realm 으로 따로 구별하기 위해서(requests 모듈 함수를 이용하려다 보니...)
auth = auth_info.replace('WWW-Authenticate: Digest realm', 'realm')
result = parse_dict_header(auth)

# digest_auth_str 만들기
digest_auth_str = make_digest_header('GET', search_url, result, 'admin', '~~QQww11')

# 만들어진 인증정보로 Data 만들기
req_header = 'GET {0} HTTP/1.1\r\nHost: 192.168.10.3:8080\r\nUser-Agent: CUSTOM PYTHON\r\nConnection: keep-alive\r\nAuthorization: {1}\r\n\r\n'
data = req_header.format(search_url, digest_auth_str)

# Data Send
s.send(data.encode('UTF-8'))

while True:
	r = s.recv(1024)
	print(r.decode('utf-8'))

 

(make_digest_header 는 Custom 하게 변경하여 만든 함수입니다.)

 

Raw String 으로 Data 를 send 하고 recv 하실 때, HTTP Header 와 Body 가 '\r\n\r\n' 으로 구분되기 때문에,

이 부분 꼭 신경써주세요!

Header 안에서 Content-Length 및 Authenficiation 영역의 구분은 '\r\n' 으로 구분됩니다.

(물론 모두 아실 것이라 믿습니다. 모르시면 이제 공부하면 되죠!)

 

이만 포스팅을 마치도록 하겠습니다.

 

오늘도 행복한 하루 되시길 바랍니다.

 

이 글을 공유합시다

facebook twitter googleplus kakaoTalk kakaostory naver band