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

2024년 2월 20일 화요일

python requests 사용시 multipart 로 사용하는 방법

python requests 를 사용해서 post를 사용하다면, 기본적으로 application/x-www-form-urlencoded 을 기본적으로 사용하게 됩니다.

그런데 간혹 multipart/form-data 를 원하는 경우가 있습니다.

기본 예제를 살펴 보겠습니다.

import requests

url = 'http://127.0.0.1:5000/funs'
data = {"key1": "value1", "key2": "value2"}

r = requests.post(url, data=data)
print(r.request.headers)
print(r.request.body)

위와 같이 사용하게 되면 'Content-Type': 'application/x-www-form-urlencoded' 이 됩니다.

{'User-Agent': 'python-requests/2.28.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '23', 'Content-Type': 'application/x-www-form-urlencoded'}
key1=value1&key2=value2

그렇다면 multipart 가 되는 조건은 뭘까요?

파일을 첨부 할때입니다.

파일 첨부 예를 만들어 보겠습니다.

import requests

url = 'http://127.0.0.1:5000/funs'
data = {"key1": "value1", "key2": "value2"}

files1 = {'fl': open('test.txt', 'rb')}
r = requests.post(url, files=files1)
print(r.request.headers)
print(r.request.body)


{'User-Agent': 'python-requests/2.28.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '149', 'Content-Type': 'multipart/form-data; boundary=40aac4e5ef113fc32999aeb43e78702e'}
b'--40aac4e5ef113fc32999aeb43e78702e\r\nContent-Disposition: form-data; name="fl"; filename="test.txt"\r\n\r\ntest123\r\n--40aac4e5ef113fc32999aeb43e78702e--\r\n'

결과가 알아보기 어렵게 되긴했지만 'Content-Type': 'multipart/form-data; 가 된것을 알 수 있습니다. 뒤에 있는 boundary는 여러개를 구분하는 용도로 사용하게 됩니다.

처음으로 돌아와서 그렇다면 어떻게 하면 data를 multipart로 전달이 가능할까요?

다른 패키지를 사용하는 방법도 있지만, requests에서는 files와 같이 보내는 방법이 있습니다.

그런데 파일을 보내지 않고 싶은데 어떻게 하냐고요? dummy로 임의의 변수를 넣어 주면 됩니다.

최종 해결책입니다.

import requests

url = 'http://127.0.0.1:5000/funs'
data = {"key1": "value1", "key2": "value2"}


files_dummy = {'fl': None}
r = requests.post(url, data=data, files=files_dummy)
print(r.request.headers)
print(r.request.body)

리턴값입니다.

{'User-Agent': 'python-requests/2.28.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '220', 'Content-Type': 'multipart/form-data; boundary=21825c01c73525d71646d70babe7f0b5'}
b'--21825c01c73525d71646d70babe7f0b5\r\nContent-Disposition: form-data; name="key1"\r\n\r\nvalue1\r\n--21825c01c73525d71646d70babe7f0b5\r\nContent-Disposition: form-data; name="key2"\r\n\r\nvalue2\r\n--21825c01c73525d71646d70babe7f0b5--\r\n'

위에서 'fl' 이란 부분을 보냈는데 requests body부분을 살펴보면 관련 내용이 없음을 알 수 있습니다.


여기에서 사용된 server 소스 입니다.

from flask import Flask, json
from flask import request
api = Flask(__name__)

@api.route('/funs', methods=['POST'])
def post_funs():
    print(request.form)
    print(request.headers)
    return json.dumps({"success": True}), 201

if __name__ == '__main__':
    api.run(debug=True)

 


2023년 10월 14일 토요일

python requests timeout 의 고찰

requests 모듈을 사용하다가 timeout이 발생하여 관련해서 찾아보고 정리해봤습니다.

Timeout 의 기초

문서에서는 아래와 같이 나와있습니다.

https://requests.readthedocs.io/en/stable/user/advanced/#timeouts

보통은 timeout 을 설정하지 않고 사용하는 경우 timeout이 얼마나 발생하는지 궁금해서 문서를 찾아봤는데 정보가 애매하게 되어있습니다.

시간 초과가 없으면 코드가 몇 분 이상 중단될 수 있습니다. 

시간 정보가 구체적이지 않았습니다.

그래서 몇가지 테스트를 해봤습니다. 아래와 같이 코드를 구현해서 timeout이 발생할 수 있는 조건으로 시험해봤습니다.

import requests
import datetime
import traceback
import time


def url_connect(url, timeout=None):
    nowtime = datetime.datetime.now()
    try:
        print(f"********** trying connect {url} *************")
        if timeout != None:
            r = requests.get(url, timeout=timeout)
        else:
            r = requests.get(url)
        print(r)
    except Exception as e:
        print("end time:", datetime.datetime.now() - nowtime)
        time.sleep(1)
        print("EXCEPTION", e)
        traceback.print_exc()


if __name__ == "__main__":
    url_connect("http://1.1.1.1:9999")
    url_connect("http://192.168.0.254")
    url_connect("http://127.0.0.1")
    url_connect("http://192.168.0.254", timeout=10)


********** trying connect http://1.1.1.1:9999 *************
end time: 0:00:21.042910
EXCEPTION HTTPConnectionPool(host='1.1.1.1', port=9999): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCD096670>: Failed to establish a new connection: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다'))
Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 174, in _new_conn
    conn = connection.create_connection(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\connection.py", line 95, in create_connection
    raise err
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\connection.py", line 85, in create_connection
    sock.connect(sa)
TimeoutError: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 703, in urlopen
    httplib_response = self._make_request(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 398, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 239, in request
    super(HTTPConnection, self).request(method, url, body=body, headers=headers)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1252, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1298, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1247, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1007, in _send_output
    self.send(msg)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 947, in send
    self.connect()
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 205, in connect
    conn = self._new_conn()
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 186, in _new_conn
    raise NewConnectionError(
urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPConnection object at 0x000001EBCD096670>: Failed to establish a new connection: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\adapters.py", line 489, in send
    resp = conn.urlopen(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 787, in urlopen
    retries = retries.increment(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\retry.py", line 592, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='1.1.1.1', port=9999): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCD096670>: Failed to establish a new connection: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:/Users/jun/Documents/GitHub/sourcecode/python/example/_52_requests/timeout.py", line 14, in url_connect
    r = requests.get(url)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\api.py", line 73, in get
    return request("get", url, params=params, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\api.py", line 59, in request
    return session.request(method=method, url=url, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\sessions.py", line 587, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\sessions.py", line 701, in send
    r = adapter.send(request, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\adapters.py", line 565, in send
    raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPConnectionPool(host='1.1.1.1', port=9999): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCD096670>: Failed to establish a new connection: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다'))
********** trying connect http://192.168.0.254 *************
end time: 0:00:21.032674
EXCEPTION HTTPConnectionPool(host='192.168.0.254', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCCC4A8B0>: Failed to establish a new connection: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다'))
Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 174, in _new_conn
    conn = connection.create_connection(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\connection.py", line 95, in create_connection
    raise err
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\connection.py", line 85, in create_connection
    sock.connect(sa)
TimeoutError: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 703, in urlopen
    httplib_response = self._make_request(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 398, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 239, in request
    super(HTTPConnection, self).request(method, url, body=body, headers=headers)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1252, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1298, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1247, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1007, in _send_output
    self.send(msg)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 947, in send
    self.connect()
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 205, in connect
    conn = self._new_conn()
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 186, in _new_conn
    raise NewConnectionError(
urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPConnection object at 0x000001EBCCC4A8B0>: Failed to establish a new connection: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\adapters.py", line 489, in send
    resp = conn.urlopen(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 787, in urlopen
    retries = retries.increment(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\retry.py", line 592, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='192.168.0.254', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCCC4A8B0>: Failed to establish a new connection: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:/Users/jun/Documents/GitHub/sourcecode/python/example/_52_requests/timeout.py", line 14, in url_connect
    r = requests.get(url)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\api.py", line 73, in get
    return request("get", url, params=params, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\api.py", line 59, in request
    return session.request(method=method, url=url, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\sessions.py", line 587, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\sessions.py", line 701, in send
    r = adapter.send(request, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\adapters.py", line 565, in send
    raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPConnectionPool(host='192.168.0.254', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCCC4A8B0>: Failed to establish a new connection: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다'))
********** trying connect http://127.0.0.1 *************
end time: 0:00:02.053697
EXCEPTION HTTPConnectionPool(host='127.0.0.1', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCD1310D0>: Failed to establish a new connection: [WinError 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다'))
Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 174, in _new_conn
    conn = connection.create_connection(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\connection.py", line 95, in create_connection
    raise err
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\connection.py", line 85, in create_connection
    sock.connect(sa)
ConnectionRefusedError: [WinError 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 703, in urlopen
    httplib_response = self._make_request(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 398, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 239, in request
    super(HTTPConnection, self).request(method, url, body=body, headers=headers)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1252, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1298, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1247, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1007, in _send_output
    self.send(msg)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 947, in send
    self.connect()
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 205, in connect
    conn = self._new_conn()
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 186, in _new_conn
    raise NewConnectionError(
urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPConnection object at 0x000001EBCD1310D0>: Failed to establish a new connection: [WinError 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\adapters.py", line 489, in send
    resp = conn.urlopen(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 787, in urlopen
    retries = retries.increment(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\retry.py", line 592, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='127.0.0.1', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCD1310D0>: Failed to establish a new connection: [WinError 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:/Users/jun/Documents/GitHub/sourcecode/python/example/_52_requests/timeout.py", line 14, in url_connect
    r = requests.get(url)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\api.py", line 73, in get
    return request("get", url, params=params, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\api.py", line 59, in request
    return session.request(method=method, url=url, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\sessions.py", line 587, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\sessions.py", line 701, in send
    r = adapter.send(request, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\adapters.py", line 565, in send
    raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCD1310D0>: Failed to establish a new connection: [WinError 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다'))
********** trying connect http://192.168.0.254 *************
end time: 0:00:10.010868
EXCEPTION HTTPConnectionPool(host='192.168.0.254', port=80): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001EBCD131910>, 'Connection to 192.168.0.254 timed out. (connect timeout=10)'))
Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 174, in _new_conn
    conn = connection.create_connection(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\connection.py", line 95, in create_connection
    raise err
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\connection.py", line 85, in create_connection
    sock.connect(sa)
socket.timeout: timed out

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 703, in urlopen
    httplib_response = self._make_request(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 398, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 239, in request
    super(HTTPConnection, self).request(method, url, body=body, headers=headers)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1252, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1298, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1247, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1007, in _send_output
    self.send(msg)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 947, in send
    self.connect()
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 205, in connect
    conn = self._new_conn()
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 179, in _new_conn
    raise ConnectTimeoutError(
urllib3.exceptions.ConnectTimeoutError: (<urllib3.connection.HTTPConnection object at 0x000001EBCD131910>, 'Connection to 192.168.0.254 timed out. (connect timeout=10)')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\adapters.py", line 489, in send
    resp = conn.urlopen(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 787, in urlopen
    retries = retries.increment(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\retry.py", line 592, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='192.168.0.254', port=80): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001EBCD131910>, 'Connection to 192.168.0.254 timed out. (connect timeout=10)'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:/Users/jun/Documents/GitHub/sourcecode/python/example/_52_requests/timeout.py", line 12, in url_connect
    r = requests.get(url, timeout=timeout)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\api.py", line 73, in get
    return request("get", url, params=params, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\api.py", line 59, in request
    return session.request(method=method, url=url, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\sessions.py", line 587, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\sessions.py", line 701, in send
    r = adapter.send(request, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\adapters.py", line 553, in send
    raise ConnectTimeout(e, request=request)
requests.exceptions.ConnectTimeout: HTTPConnectionPool(host='192.168.0.254', port=80): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001EBCD131910>, 'Connection to 192.168.0.254 timed out. (connect timeout=10)'))

call stack 부분을 빼고 다시 정리해봤습니다.

********** trying connect http://1.1.1.1:9999 *************
end time: 0:00:21.042910
EXCEPTION HTTPConnectionPool(host='1.1.1.1', port=9999): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCD096670>: Failed to establish a new connection: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다'))
********** trying connect http://192.168.0.254 *************
end time: 0:00:21.032674
EXCEPTION HTTPConnectionPool(host='192.168.0.254', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCCC4A8B0>: Failed to establish a new connection: [WinError 10060] 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다'))
********** trying connect http://127.0.0.1 *************
end time: 0:00:02.053697
EXCEPTION HTTPConnectionPool(host='127.0.0.1', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001EBCD1310D0>: Failed to establish a new connection: [WinError 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다'))
********** trying connect http://192.168.0.254 *************
end time: 0:00:10.010868
EXCEPTION HTTPConnectionPool(host='192.168.0.254', port=80): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001EBCD131910>, 'Connection to 192.168.0.254 timed out. (connect timeout=10)'))

test code가 크게 4가지 인데요.

1.1.1.1:9999 의 주소는 응답을 안할꺼라는 가정하에 테스트 해봤습니다.

21초 걸렸고 Max retries exceeded 트라이가 초과되었다? 라는 애매한 표현을 하고 있습니다.

두번째는 내부 공유기 쪽으로 안쓰는 포트에 시도해보았습니다.

마찬가지로 21초가 걸렸습니다.

세번째 127.0.0.1 은 내부 포트로 2초만에 응답이 없습니다. timeout이 아니고 거부 형태입니다.

마지막은 timeout을 10초로 넣고 두번째 테스트 했던 내부 공유기 주소로 사용해봤습니다.

10초에 Max retries exceeded 부분은 동일하나 뒤쪽에 보면 timed out 이 발생함을 알 수 있습니다.

위 테스트에서 21초가 걸리긴 했지만 그것을 내부 기본 timeout이라고 할 수는 없습니다. 내부적으로 소켓에 대한 타임 아웃이 있고 그걸 retry 하는 형태로 구현이 되어 있음을 알 수 있으며, 우리가 설정하는 timeout 과는 Exception 이 다름을 알 수 있었습니다.


사용법

타임 아웃이 필요한경우는

r = requests.get('https://github.com', timeout=5)

위와 같이 사용해주면 되는데, 2가지 종류의 시간을 설정이 가능합니다. connect, read 시간입니다. connect timeout은 일반적으로 listen 하고 있는 서버에 접속하면 accept (연결) 를 하게되는데 그때까지의 시간 이라고 보면 됩니다.

The connect timeout is the number of seconds Requests will wait for your client to establish a connection to a remote machine (corresponding to the connect()) call on the socket.

read 시간은 데이터를 주고 받을때의 시간입니다. 클라이언트가 서버에서 전송된 바이트 사이에 대기하는 시간(초)입니다.

(Specifically, it’s the number of seconds that the client will wait between bytes sent from the server. In 99.9% of cases, this is the time before the server sends the first byte).

r = requests.get('https://github.com', timeout=(3.05, 27))

timeout=(connect timeout, read time) 형태로 설정합니다.

무한정 기다리는 경우 아래와 같이 합니다. 그런데 앞에서 테스트 해봤듯이 꼭 무한정이 되는것은 아닙니다.

r = requests.get('https://github.com', timeout=None)


Timeout 을 넘어서

여기에서 한가지 궁금한 점이 생겼습니다. requests에서 파일을 받거나 web page를 받을때의 timeout은 전체 크기를 받는 부분의 timeout 인가라는 의문입니다.

예를 들어 100MB의 파일을 받는 상황이고 requests.get의 timeout이 1초 일때 100MB를 1초만에 받지 못하면 timeout이 발생하는가입니다.

그래서 예제를 만들어 봤습니다.

웹 서버를 구성해야하는 예제이므로 복잡하지만 간단하게 예제를 위해서 socket 으로 설계했습니다.


import socket


IP = '0.0.0.0'
PORT = 8080
ADDR = (IP, PORT)


def make_dummy_message(len):
    ret = ""
    for i in range(0, len):
        ret = ret + 'A'
    return ret

head_data = f"""HTTP/1.1 200 OK

Server: Werkzeug/2.2.2 Python/3.8.10
Date: Sat, 14 Oct 2023 01:43:25 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 100000000
Connection: close

<!doctype html>
<html lang=en>
"""

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
    server_socket.bind(ADDR)
    server_socket.listen()
    print(f"listen [{ADDR}]")

    while True:
        client_socket, client_addr = server_socket.accept()
        print(f"accept [{client_addr}]")
        # msg = client_socket.recv(SIZE)
        print(head_data)
        client_socket.sendall(head_data.encode())
        for i in range(1, 100):
            print(f"send {i}")
            client_socket.sendall(make_dummy_message(1000000).encode())

        client_socket.close()

web 소켓을 열어서 http 응답을 주는 형태입니다. make_dummy_message 에 의해서 약 1MB의 메세지를 client_socket.sendall 함수로 전달해 줍니다.

        for i in range(1, 100):
            print(f"send {i}")
            client_socket.sendall(make_dummy_message(1000000).encode())

1MB씩 100번 100MB가 전달되게 됩니다.


이제 client 코드 입니다.

import requests
import datetime
import traceback
import time
import socket

def url_connect(url, timeout=None):
    nowtime = datetime.datetime.now()
    try:
        print(f"********** trying connect {url} *************")
        if timeout != None:
            r = requests.get(url, timeout=timeout)
        else:
            r = requests.get(url)
        print(r)
    except Exception as e:
        print("end time:", datetime.datetime.now() - nowtime)
        time.sleep(1)
        print("EXCEPTION", e)
        traceback.print_exc()


if __name__ == "__main__":
   s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
   s.connect(("8.8.8.8", 80))
   myip = s.getsockname()[0]
   print(myip)
   s.close()
   url_connect(f"http://{myip}:8080", timeout=1)

나의 ip를 구한다음 8080 포트로 접속해 보는것입니다.

서버를 실행하지 않은 상태는 1초만에 아래 형태로 나타납니다.

192.168.0.35
********** trying connect http://192.168.0.35:8080 *************
end time: 0:00:01.013626
EXCEPTION HTTPConnectionPool(host='192.168.0.35', port=8080): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001A957F37670>, 'Connection to 192.168.0.35 timed out. (connect timeout=1)'))
Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 174, in _new_conn
    conn = connection.create_connection(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\connection.py", line 95, in create_connection
    raise err
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\connection.py", line 85, in create_connection
    sock.connect(sa)
socket.timeout: timed out

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 703, in urlopen
    httplib_response = self._make_request(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 398, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 239, in request
    super(HTTPConnection, self).request(method, url, body=body, headers=headers)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1252, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1298, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1247, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 1007, in _send_output
    self.send(msg)
  File "C:\Users\jun\AppData\Local\Programs\Python\Python38\lib\http\client.py", line 947, in send
    self.connect()
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 205, in connect
    conn = self._new_conn()
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connection.py", line 179, in _new_conn
    raise ConnectTimeoutError(
urllib3.exceptions.ConnectTimeoutError: (<urllib3.connection.HTTPConnection object at 0x000001A957F37670>, 'Connection to 192.168.0.35 timed out. (connect timeout=1)')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\adapters.py", line 489, in send
    resp = conn.urlopen(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\connectionpool.py", line 787, in urlopen
    retries = retries.increment(
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\urllib3\util\retry.py", line 592, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='192.168.0.35', port=8080): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001A957F37670>, 'Connection to 192.168.0.35 timed out. (connect timeout=1)'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:/Users/jun/Documents/GitHub/sourcecode/python/example/_52_requests/timeout3.py", line 12, in url_connect
    r = requests.get(url, timeout=timeout)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\api.py", line 73, in get
    return request("get", url, params=params, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\api.py", line 59, in request
    return session.request(method=method, url=url, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\sessions.py", line 587, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\sessions.py", line 701, in send
    r = adapter.send(request, **kwargs)
  File "C:\Users\jun\Documents\GitHub\sourcecode\venv\lib\site-packages\requests\adapters.py", line 553, in send
    raise ConnectTimeout(e, request=request)
requests.exceptions.ConnectTimeout: HTTPConnectionPool(host='192.168.0.35', port=8080): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001A957F37670>, 'Connection to 192.168.0.35 timed out. (connect timeout=1)'))

서버를 실행한 다음에는 다른 에러가 발생하는데

192.168.0.35
********** trying connect http://192.168.0.35:8080 *************
end time: 0:00:11.814797
EXCEPTION ("Connection broken: ConnectionResetError(10054, '현재 연결은 원격 호스트에 의해 강제로 끊겼습니다', None, 10054, None)", ConnectionResetError(10054, '현재 연결은 원격 호스트에 의해 강제로 끊겼습니다', None, 10054, None))

대략 위와 같이 11초 후에 에러같은 것이 발생합니다.

몇가지 이유가 있는데 서버쪽을 대충 만들어서 그렇습니다. contents크기를 받고 소켓을 닫도록 되어있는데 해당 부분이 적절치 않게 만들었기 때문입니다.

timeout시간만 보려고 테스트 코드를 만든것이라 에러에 대해서는 무시하면됩니다.

11초가 걸렸는데도 1초 timeout이 발생하지 않았다는 의미는 구간 구간 socket read 시 1초 timeout이 발생할때만 멈춤다는 것을 알 수 있습니다.




2023년 4월 2일 일요일

requests 를 이용한 파일 다운로드 하기, file name 정하기

 requests 를 이용할때 http상에 존재하는 파일을 다운로드 하는 방법입니다.

저장할 파일이름 설정도 필요하기 때문에 정리해 보았습니다.


다운로드 하는 파일 이름 구하기 

http 6266 규격의 Content-Disposition: 여기로부터 파일 이름을 구할 수 있습니다.

https://www.rfc-editor.org/rfc/rfc6266

하지만 이 규격은 필수 규격이 아니라 해당 규격대로 오지 않는 경우가 있습니다. 이 부분을 고려해서 구현하였습니다.

pip install pyrfc6266 

pyrfc6266 패키지를 이용하여 requests 결과를 넘기면 파일 이름을 얻을 수 있습니다.

r = requests.get(url, stream=True)
filename = pyrfc6266.requests_response_to_filename(r)


다운로드 하는 파일을 조금씩 저장하기

requests 패키지는 기본적으로 웹페이지 로딩이 완료되면 다음으로 넘어가게 되어 있습니다. 만약 다운로드 하는 파일이 큰 경우 해당 크기 만큼 메모리가 필요하기 때문에 다운로드 안되는 경우도 존재합니다. 

이때 사용하는 것이 stream=True 인자입니다.

r = requests.get(url, stream=True)
for chunk in r.iter_content(chunk_size=1024 * 8):
if chunk:
f.write(chunk)

위와 같은 형태로 특정 크기( chunk ) 단위로 파일 저장이 가능합니다.

파일이 메모리상에만 존재하는 경우가 있기 때문에 flush를 해주는 경우가 안전성을 위해서 좋습니다. 이것을 구현한 것이 아래 코드입니다.

f.flush()
os.fsync(f.fileno())


다음은 전체 코드입니다.

import requests
import os
import pyrfc6266
import urllib.parse


# url 은 get 방식으로 다운로드 하기 위한 주소가 됩니다.
# path 는 다운로드 경로가 됩니다.
# 성공시 Full path 파일 이름이 넘어옵니다.
def download_get(url, dest_folder="."):
    if not os.path.exists(dest_folder):
        os.makedirs(dest_folder)  # create folder if it does not exist
    r = requests.get(url, stream=True)
    filename = pyrfc6266.requests_response_to_filename(r)
    print(r.headers, filename)
    if filename == "" or filename is None:
        filename = url.split('/')[-1].replace(" ", "_")
    elif '%' in filename:
        filename = urllib.parse.unquote(filename)

    file_path = os.path.join(dest_folder, filename)

    if r.ok:
        print("saving to", os.path.abspath(file_path))
        with open(file_path, 'wb') as f:
            for chunk in r.iter_content(chunk_size=1024 * 512):
                if chunk:
                    f.write(chunk)
                    f.flush()
                    os.fsync(f.fileno())
    else:
        print("Download failed: status code {}\n{}".format(r.status_code, r.text))
        return None
    return file_path

if __name__ == "__main__":
    url = 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png'
    print(download_get(url, "."))
    url = 'https://cdn.hancom.com/link/docs/%ED%95%9C%EA%B8%80%EB%AC%B8%EC%84%9C%ED%8C%8C%EC%9D%BC%ED%98%95%EC%8B%9D3.0_HWPML_revision1.2.hwp'
    print(download_get(url, "."))


실행 결과

{'Accept-Ranges': 'bytes', 'Content-Type': 'image/png', 'Cross-Origin-Resource-Policy': 'cross-origin', 'Cross-Origin-Opener-Policy-Report-Only': 'same-origin; report-to="static-on-bigtable"', 'Report-To': '{"group":"static-on-bigtable","max_age":2592000,"endpoints":[{"url":"https://csp.withgoogle.com/csp/report-to/static-on-bigtable"}]}', 'Content-Length': '5969', 'Date': 'Sun, 02 Apr 2023 10:07:31 GMT', 'Expires': 'Sun, 02 Apr 2023 10:07:31 GMT', 'Cache-Control': 'private, max-age=31536000', 'Last-Modified': 'Tue, 22 Oct 2019 18:30:00 GMT', 'X-Content-Type-Options': 'nosniff', 'Server': 'sffe', 'X-XSS-Protection': '0', 'Alt-Svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000'} googlelogo_color_272x92dp.png
saving to C:\Users\Documents\GitHub\sourcecode\python\example\_52_requests\googlelogo_color_272x92dp.png
.\googlelogo_color_272x92dp.png
{'Content-Type': 'application/octet-stream', 'Accept-Ranges': 'bytes', 'X-Agile-Brick-Id': '480527996', 'X-Agile-Checksum': '1121cdabe5929d7aa68704d0516488852bcdc78b4f4b368ad054e8c121d04023', 'X-Agile-Request-Id': '5aaf564a732da062c1c10bf592122357, 24385af8bc6afbe50e767494734b7cfb', 'X-Agile-Source': '111.119.25.183:1987', 'Server': 'CloudStorage', 'Age': '188002', 'Date': 'Sun, 02 Apr 2023 10:07:32 GMT', 'Last-Modified': 'Mon, 10 Nov 2014 01:08:00 GMT', 'X-LLID': '524b2b419aadef6e7827af6fb89c006a', 'Content-Length': '1310720', 'Access-Control-Allow-Origin': '*'} %ED%95%9C%EA%B8%80%EB%AC%B8%EC%84%9C%ED%8C%8C%EC%9D%BC%ED%98%95%EC%8B%9D3.0_HWPML_revision1.2.hwp
saving to C:\Users\Documents\GitHub\sourcecode\python\example\_52_requests\한글문서파일형식3.0_HWPML_revision1.2.hwp
.\한글문서파일형식3.0_HWPML_revision1.2.hwp


2021년 10월 3일 일요일

python post/get requests( 파이썬에서 requests를 이용한 POST/GET requests시 내용 보기 )

POST/GET 은 클라이언트가 서버에 web page를 요청하는 방법입니다. 

즉 우리가 http://128.0.0.1/ 이라고 typing 하면 여기까지는 URL 이 되고, 인자를 어떻게 보낼지 결정하는것이 POST / GET 입니다.


1. GET

URL에 변수(데이터)를 포함시켜 요청합니다.

예를들어 보겠습니다. 웹에 로그인 페이지가 있고 id와 pw를 입력받는다고 할때 GET에서는 아래 방식으로 URL에 해당 정보를 포함할 수 있습니다. URL에 정보가 노출 되는 방식 입니다.

http://128.0.0.1/login?id=test&pw=go


2. POST

http://128.0.0.1/login 이렇게 URL을 요청하고

나머지 id=test , pw=go 는 다른 방식으로 전달합니다.

방법은 좀 더 다양하고 복잡합니다. 왜냐하면 읽는쪽에서 이것을 어떻게 읽을거냐에 대한 약속이 필요하기 때문입니다.


방법은 많이들 알고 있는데 이것 두개가 항상 항상 헷갈립니다. 그냥 외웁시다. GET은 URL을 변형해서 요청 한다... ^^;


여기에서는 python requests 모듈과 flask 를 이용한 서버 및 테스트 하면서 정리해 보겠습니다.


3. Flask 사용

웹서버 형태로도 시험을 해도 되지만 http의 GET / POST 을 처리할수 있기만 하면 되어서, 간단하게 UI도 없는 형태라 flask가 적당해서 구현하였습니다. 


flask로 만든 서버 소스

from flask import Flask, json, request

funs1 = [{"id": 1, "name": "fun1"}, {"id": 2, "name": "fun2"}]
funs2 = [{"id": 3, "name": "fun1"}, {"id": 4, "name": "fun2"}]

api = Flask(__name__)

@api.route('/funs', methods=['GET'])
def get_funs():
	print("get request.args:",request.args.to_dict())
	print("get request.json:",request.json)
	print("get request.form:",request.form.to_dict())
	print("get request.getdata:",request.get_data())
	return json.dumps(funs1), 200

@api.route('/funs', methods=['POST'])
def post_funs():
	print("post request.args:",request.args.to_dict())
	print("post request.json:",request.json)
	print("post request.form:",request.form.to_dict())
	print("post request.getdata:",request.get_data())
	return json.dumps(funs2), 201

if __name__ == '__main__':
    api.run(debug=True)


flask의 /funs URL을 접속할때 post, get 등이 어떠한 값형태를 취하는지 구현하였습니다.

이때 201,200 을 취하도록 처리하였기 때문에(함수의 리턴 부분 참고) 해당 값에 대해서는 특별히 주의 하지 않아도 됩니다.(이말은 보통 http의경우 200이 오류 없음을 나타내는데 201이 와도 특별하게 처리를 안해줘도 된다는 의미입니다. 여기에서는 단순히 구별하는 용도록 차이를 두었습니다.)


4. requests로 만드는 post, get

request의 post, get함수가 있는데 뒤쪽의 params, data, json 인자에 대해서 확인이 필요하기 때문에, 각각 post/get 에 대하여 여러가지 방식으로 전달하도록 구현해보았습니다.

import requests

def print_roundtrip(response, *args, **kwargs):
	format_headers = lambda d: '\n'.join(f'{k}: {v}' for k, v in d.items())
	print((""
		"---------------- request ----------------\n"
		"{req.method} {req.url}\n"
		"{reqhdrs}\n"
		"\n"
		"{req.body}\n"
		"---------------- response ----------------\n"
		"{res.status_code} {res.reason} {res.url}\n"
		"{reshdrs}\n"
		"\n"
		"{res.text}\n"
		"").format(
		req=response.request, 
		res=response, 
		reqhdrs=format_headers(response.request.headers), 
		reshdrs=format_headers(response.headers), 
	))

params = {'param1':'v1','param2':2}
params['param0']=1
print("requests.get('http://127.0.0.1:5000/funs', params=params")
resp = requests.get('http://127.0.0.1:5000/funs', params=params, hooks={'response': print_roundtrip})
print("**************")
params['param0']=2
print("requests.get('http://127.0.0.1:5000/funs', data=params")
resp = requests.get('http://127.0.0.1:5000/funs', data=params, hooks={'response': print_roundtrip})
print("**************")
params['param0']=3
print("requests.post('http://127.0.0.1:5000/funs', params=params")
resp = requests.post('http://127.0.0.1:5000/funs', params=params, hooks={'response': print_roundtrip})
print("**************")
params['param0']=4
print("requests.post('http://127.0.0.1:5000/funs', data=params")
resp = requests.post('http://127.0.0.1:5000/funs', data=params, hooks={'response': print_roundtrip})
print("**************")
params['param0']=5
print("requests.post('http://127.0.0.1:5000/funs', json=params")
resp = requests.post('http://127.0.0.1:5000/funs', json=params, hooks={'response': print_roundtrip})
print("**************")

그전에 requests의 주고받는 데이터 정보 디버깅용으로 출력할 필요가 있는데, 그것이 hooks를 이용하는 방법입니다.

위에서는 hooks={'response': print_roundtrip} 부분을 참고하면 됩니다.


5. 실행 결과

서버쪽 실행결과

C:\Users\USER\Documents\GitHub\sourcecode\python\example>python _18_requests_get_post_server.py
 * Serving Flask app "_18_requests_get_post_server" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 298-679-048
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
get request.args: {'param1': 'v1', 'param2': '2', 'param0': '1'}
get request.json: None
get request.form: {}
get request.getdata: b''
127.0.0.1 - - [03/Oct/2021 13:47:46] "GET /funs?param1=v1&param2=2&param0=1 HTTP/1.1" 200 -
get request.args: {}
get request.json: None
get request.form: {'param1': 'v1', 'param2': '2', 'param0': '2'}
get request.getdata: b''
127.0.0.1 - - [03/Oct/2021 13:47:46] "GET /funs HTTP/1.1" 200 -
post request.args: {'param1': 'v1', 'param2': '2', 'param0': '3'}
post request.json: None
post request.form: {}
post request.getdata: b''
127.0.0.1 - - [03/Oct/2021 13:47:46] "POST /funs?param1=v1&param2=2&param0=3 HTTP/1.1" 201 -
post request.args: {}
post request.json: None
post request.form: {'param1': 'v1', 'param2': '2', 'param0': '4'}
post request.getdata: b''
127.0.0.1 - - [03/Oct/2021 13:47:46] "POST /funs HTTP/1.1" 201 -
post request.args: {}
post request.json: {'param1': 'v1', 'param2': 2, 'param0': 5}
post request.form: {}
post request.getdata: b'{"param1": "v1", "param2": 2, "param0": 5}'
127.0.0.1 - - [03/Oct/2021 13:47:46] "POST /funs HTTP/1.1" 201 -


클라이언트쪽 실행 결과

C:\Users\USER\Documents\GitHub\sourcecode\python\example>python _18_requests_get_post.py
requests.get('http://127.0.0.1:5000/funs', params=params
---------------- request ----------------
GET http://127.0.0.1:5000/funs?param1=v1&param2=2&param0=1
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

None
---------------- response ----------------
200 OK http://127.0.0.1:5000/funs?param1=v1&param2=2&param0=1
Content-Type: text/html; charset=utf-8
Content-Length: 54
Server: Werkzeug/1.0.1 Python/3.8.3
Date: Sun, 03 Oct 2021 04:47:46 GMT

[{"id": 1, "name": "fun1"}, {"id": 2, "name": "fun2"}]

**************
requests.get('http://127.0.0.1:5000/funs', data=params
---------------- request ----------------
GET http://127.0.0.1:5000/funs
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 27
Content-Type: application/x-www-form-urlencoded

param1=v1&param2=2&param0=2
---------------- response ----------------
200 OK http://127.0.0.1:5000/funs
Content-Type: text/html; charset=utf-8
Content-Length: 54
Server: Werkzeug/1.0.1 Python/3.8.3
Date: Sun, 03 Oct 2021 04:47:46 GMT

[{"id": 1, "name": "fun1"}, {"id": 2, "name": "fun2"}]

**************
requests.post('http://127.0.0.1:5000/funs', params=params
---------------- request ----------------
POST http://127.0.0.1:5000/funs?param1=v1&param2=2&param0=3
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 0

None
---------------- response ----------------
201 CREATED http://127.0.0.1:5000/funs?param1=v1&param2=2&param0=3
Content-Type: text/html; charset=utf-8
Content-Length: 54
Server: Werkzeug/1.0.1 Python/3.8.3
Date: Sun, 03 Oct 2021 04:47:46 GMT

[{"id": 3, "name": "fun1"}, {"id": 4, "name": "fun2"}]

**************
requests.post('http://127.0.0.1:5000/funs', data=params
---------------- request ----------------
POST http://127.0.0.1:5000/funs
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 27
Content-Type: application/x-www-form-urlencoded

param1=v1&param2=2&param0=4
---------------- response ----------------
201 CREATED http://127.0.0.1:5000/funs
Content-Type: text/html; charset=utf-8
Content-Length: 54
Server: Werkzeug/1.0.1 Python/3.8.3
Date: Sun, 03 Oct 2021 04:47:46 GMT

[{"id": 3, "name": "fun1"}, {"id": 4, "name": "fun2"}]

**************
requests.post('http://127.0.0.1:5000/funs', json=params
---------------- request ----------------
POST http://127.0.0.1:5000/funs
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 42
Content-Type: application/json

b'{"param1": "v1", "param2": 2, "param0": 5}'
---------------- response ----------------
201 CREATED http://127.0.0.1:5000/funs
Content-Type: text/html; charset=utf-8
Content-Length: 54
Server: Werkzeug/1.0.1 Python/3.8.3
Date: Sun, 03 Oct 2021 04:47:46 GMT

[{"id": 3, "name": "fun1"}, {"id": 4, "name": "fun2"}]

**************


6. 정리 및 결론

post, get과 관계없이 params 으로 넘기냐 data로 넘기느냐 web 방식의 차이가 있음

GET/POST 이부분은 request할때 아래 부분이 달라집니다.

---------------- request ----------------

POST http://127.0.0.1:5000/funs

---------------- request ----------------

GET http://127.0.0.1:5000/funs

이러한 사실은 서버쪽에서 처리하는 방식의 차이로 이루어집니다.

GET/POST 관계 없이 params=params 의 경우 params 데이터를 이용하여 자체적으로 url뒤쪽에 데이터를 만들게 됩니다.

즉 3번째 POST 임에도 params를 사용하는 예제의 결과 참고

resp = requests.post('http://127.0.0.1:5000/funs', params=params, hooks={'response': print_roundtrip})

결과는 param으로 들어온 인자가 GET에서 사용하는 URL 형태로 변형됩니다.

POST http://127.0.0.1:5000/funs?param1=v1&param2=2&param0=3


GET/POST 관계 없이 data=params 를 사용하게 되면 아래 형태로 전달하게 됩니다.

Content-Type: application/x-www-form-urlencoded

2/4번 예제참고

json을 사용하게 되면 Content-Type: application/json 이용하여 전달하게 됩니다.

서버측 분석을 결과로 볼때 Content-Type 에따라서 읽는 방식이 달라집니다.


7. 총정리

requests 사용 인자에 따른 분류

params 사용시

    URL 사용 

    Content 사용안함

    서버 request.args 로 읽어야함

    GET 사용시 사용해야함

json 사용시

    Content로 전달

    Content-Type: application/json

    서버 request.json , request.get_data() 읽음

    서버가 application/json 데이터를 받을 수 있을때 사용

    POST 로 사용가능

data 사용시

    Content로 전달 

    Content-Type: application/x-www-form-urlencoded

    form의 전송 버튼 사용시 사용함

    서버 request.get_data() 읽음

    POST 로 사용가능