Python: REST/HTTP API server/client
REST
서버
- Flask, Flask-RESTX 이용
- Flask API Reference
- Flask-RESTX API Reference
- 설치
pip install Flaskpip install flask-restx
- GET을 등록하면 HEAD method가 자동으로 추가
- Flask v0.6부터는 OPTION method를 자동으로 처리
- Flask-RESTX를 통해 Swagger 제공
클라이언트
- Requests 이용
- API Reference
- 설치
pip install requests
예제
- 코드
-
서버
-
import logging
from flask import Flask, request, jsonify, redirect, url_for, make_response
from flask_restx import Resource, Namespace, Api, fields
index1 = Namespace(name='index1-name',
path='/index1',
description='index1 description')
@index1.route('', methods=['GET'])
class Index1(Resource):
def get(self):
return redirect(url_for('index2-name_index2'))
index2 = Namespace(name='index2-name',
path='/index2',
description='index2 description')
@index2.route('', methods=['GET'])
class Index2(Resource):
def get(self):
print("\n\n")
print("request.method :", request.method)
print("request.url :", request.url)
print("request.url_root :", request.url_root)
print("request.mimetype :", request.mimetype)
print("request.headers :", request.headers)
print("request.remote_addr :", request.remote_addr)
if "key" in request.headers:
print("request.headers[key] :", request.headers["key"])
return jsonify({"result": "index2 ok"})
uri = Namespace(name='uri-name', path='/uri', description='uri description')
uri_post_put_request = uri.model(name='uri_post_put_request',
model={
'key':
fields.String(description='description',
required=True,
example="value")
})
uri_get_response = uri.model(name='uri_get_response',
model={
'id':
fields.Integer(description='description',
required=True,
example=1),
'key':
fields.String(description='description',
required=True,
example="value")
})
@uri.route('', methods=['GET', 'POST', 'PUT'])
@uri.route('/<uri_id_1>', methods=['GET'])
@uri.route('/<int:uri_id_2>', methods=['DELETE'])
class UriGet(Resource):
@uri.response(200, 'OK', uri_get_response)
@uri.response(500, 'Failed')
def get(self, uri_id_1=None):
print("\n\n")
print("request.url :", request.url)
print("uri_id_1 :", uri_id_1)
print("request.query_string :", request.query_string)
print("key :", request.args.get('key', ''))
response = {"key": "value"}
return jsonify(response)
@uri.expect(uri_post_put_request)
@uri.doc(responses={201: "Created"})
@uri.doc(responses={500: "Internal Server Error"})
def post(self):
print("\n\n")
print("request.method :", request.method)
print("request.content_type :", request.content_type)
print("request.content_length :", request.content_length)
print("request.is_json :", request.is_json)
if request.is_json:
print("request.get_json() :", request.get_json())
else:
print("request.form :", request.form)
response = make_response('', 201 if request.method == 'POST' else 200)
return response
@uri.expect(uri_post_put_request)
@uri.response(200, 'OK')
def put(self):
self.post()
@uri.response(204, 'No Content')
@uri.response(404, 'Not Found')
def delete(self, uri_id_2):
print("\n\n")
print("uri_id_2 :", uri_id_2)
print("request.remote_addr :", request.remote_addr)
response = make_response('', 204)
response.headers['X-TEST'] = 'value'
return response
if __name__ == "__main__":
app = Flask(__name__)
api = Api(
app=app,
version='1.0',
title='title',
description="description",
contact='contact',
contact_email='contact_email',
)
api.add_namespace(index1)
api.add_namespace(index2)
api.add_namespace(uri)
logging.basicConfig(level=logging.DEBUG)
index2.logger.addHandler(logging.FileHandler("index2.log"))
uri.logger.setLevel(logging.CRITICAL)
print("\n")
app.logger.debug('debug log')
index1.logger.info('info log')
index2.logger.warning('warning log')
uri.logger.error('error log')
uri.logger.critical('critical log')
print("\n")
app.run(debug=True, host='0.0.0.0', port=10000)
-
클라이언트
import requests
import logging
if __name__ == "__main__":
logging.basicConfig()
logging.getLogger().setLevel(logging.INFO)
logging.debug('debug log')
logging.info('info log')
logging.warning('warning log')
logging.error('error log')
logging.critical('critical log')
print("\n")
print("----- status code ----- start")
print("\trequests.codes.ok :", requests.codes.ok)
print("\trequests.codes.created :", requests.codes.created)
print("\trequests.codes.no_content :", requests.codes.no_content)
print("\t...")
print("----- status code ----- end\n\n")
request = requests.options('http://127.0.0.1:10000/index1')
print("----- options", request.url, "----- start")
print("\trequest.status_code :", request.status_code)
print("\trequest.headers.get('allow') :", request.headers.get('allow'))
print("----- options", request.url, "----- end\n\n")
request = requests.options('http://127.0.0.1:10000/uri')
print("----- options", request.url, "----- start")
print("\trequest.status_code :", request.status_code)
print("\trequest.headers.get('allow') :", request.headers.get('allow'))
print("----- options", request.url, "----- end\n\n")
url = 'http://127.0.0.1:10000/index1'
headers = {'key': 'value'}
request = requests.get(url, headers=headers)
print("----- get", url, "----- start")
print("\trequest.status_code :", request.status_code)
print("\trequest.headers :", request.headers)
print("\trequest.headers['Content-Type'] :",
request.headers['Content-Type'])
print("\trequest.headers.get('content-type') :",
request.headers.get('content-type'))
print("\trequest.text :", request.text)
print("\trequest.encoding :", request.encoding)
print("\trequest.content :", request.content)
print("\trequest.json() :", request.json())
print("\trequest.history :", request.history)
print("----- get", request.url, "----- end\n\n")
request = requests.head('http://127.0.0.1:10000/uri')
print("----- head", request.url, "----- start")
print("\trequest.status_code :", request.status_code)
print("\trequest.headers :", request.headers)
print("----- head", request.url, "----- end\n\n")
request = requests.get('http://127.0.0.1:10000/uri', timeout=3)
print("----- get", request.url, "----- start")
print("\trequest.status_code :", request.status_code)
print("\trequest.headers :", request.headers)
print("\trequest.headers.get('content-type') :",
request.headers.get('content-type'))
print("\trequest.json() :", request.json())
print("----- get", request.url, "----- end\n\n")
request = requests.get('http://127.0.0.1:10000/uri/id')
print("----- get", request.url, "----- start")
print("\trequest.status_code :", request.status_code)
print("\trequest.headers.get('content-type') :",
request.headers.get('content-type'))
print("\trequest.json() :", request.json())
print("----- get", request.url, "----- end\n\n")
payload = {'key': 'value'}
request = requests.get('http://127.0.0.1:10000/uri', params=payload)
print("----- get", request.url, "----- start")
print("\trequest.status_code :", request.status_code)
print("\trequest.headers.get('content-type') :",
request.headers.get('content-type'))
print("\trequest.json() :", request.json())
print("----- get", request.url, "----- end\n\n")
payload = {'key1': 'value1', 'key2': ['value2-1', 'value2-2']}
request = requests.get('http://127.0.0.1:10000/uri', params=payload)
print("----- get", request.url, "----- start")
print("\trequest.status_code :", request.status_code)
print("----- get", request.url, "----- end\n\n")
payload = {'key': 'value'}
request = requests.post('http://127.0.0.1:10000/uri', data=payload)
print("----- post", request.url, "----- 1 start")
print("\trequest.status_code :", request.status_code)
print("----- post", request.url, "----- 1 end\n\n")
payload = {'key': 'value'}
request = requests.post('http://127.0.0.1:10000/uri', json=payload)
print("----- post", request.url, "----- 2 start")
print("\trequest.status_code :", request.status_code)
print("----- post", request.url, "----- 2 end\n\n")
payload_tuples = [('key1', 'value1'), ('key1', 'value2')]
request = requests.put('http://127.0.0.1:10000/uri', json=payload_tuples)
print("----- put", request.url, "----- 1 start")
print("\trequest.status_code :", request.status_code)
print("----- put", request.url, "----- 1 end\n\n")
payload_dict = {'key1': ['value1', 'value2']}
request = requests.put('http://127.0.0.1:10000/uri', json=payload_dict)
print("----- put", request.url, "----- 2 start")
print("\trequest.status_code :", request.status_code)
print("----- put", request.url, "----- 2 end\n\n")
request = requests.delete('http://127.0.0.1:10000/uri/id')
print("----- delete", request.url, "----- 1 start")
print("\trequest.status_code :", request.status_code)
print("\trequest.headers.get('x-test') :", request.headers.get('x-test'))
print("\trequest.json() :", request.json())
print("----- delete", request.url, "----- 1 end\n\n")
request = requests.delete('http://127.0.0.1:10000/uri/123')
print("----- delete", request.url, "----- 2 start")
print("\trequest.status_code :", request.status_code)
print("\trequest.headers.get('x-test') :", request.headers.get('x-test'))
print("----- delete", request.url, "----- 2 end\n\n")
- Swagger
http://127.0.0.1:10000/



- 실행 결과
-
서버
-
$ python server.py
[2023-04-19 11:23:25,331] DEBUG in main: debug log
DEBUG:main:debug log
[2023-04-19 11:23:25,331] INFO in main: info log
INFO:flask_restx.namespace.index1-name:info log
[2023-04-19 11:23:25,331] WARNING in main: warning log
WARNING:flask_restx.namespace.index2-name:warning log
[2023-04-19 11:23:25,331] CRITICAL in main: critical log
CRITICAL:flask_restx.namespace.uri-name:critical log
* Serving Flask app 'main'
* Debug mode: on
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:10000
* Running on http://192.168.14.220:10000
INFO:werkzeug:Press CTRL+C to quit
INFO:werkzeug: * Restarting with stat
[2023-04-19 11:23:25,567] DEBUG in main: debug log
DEBUG:main:debug log
[2023-04-19 11:23:25,567] INFO in main: info log
INFO:flask_restx.namespace.index1-name:info log
[2023-04-19 11:23:25,567] WARNING in main: warning log
WARNING:flask_restx.namespace.index2-name:warning log
[2023-04-19 11:23:25,567] CRITICAL in main: critical log
CRITICAL:flask_restx.namespace.uri-name:critical log
WARNING:werkzeug: * Debugger is active!
INFO:werkzeug: * Debugger PIN: 774-693-356
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "OPTIONS /index1 HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "OPTIONS /uri HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "GET /index1 HTTP/1.1" 302 -
request.method : GET
request.url : http://127.0.0.1:10000/index2
request.url_root : http://127.0.0.1:10000/
request.mimetype :
request.headers : Host: 127.0.0.1:10000
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Key: value
request.remote_addr : 127.0.0.1
request.headers[key] : value
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "GET /index2 HTTP/1.1" 200 -
request.url : http://127.0.0.1:10000/uri
uri_id_1 : None
request.query_string : b''
key :
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "HEAD /uri HTTP/1.1" 200 -
request.url : http://127.0.0.1:10000/uri
uri_id_1 : None
request.query_string : b''
key :
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "GET /uri HTTP/1.1" 200 -
request.url : http://127.0.0.1:10000/uri/id
uri_id_1 : id
request.query_string : b''
key :
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "GET /uri/id HTTP/1.1" 200 -
request.url : http://127.0.0.1:10000/uri?key=value
uri_id_1 : None
request.query_string : b'key=value'
key : value
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "GET /uri?key=value HTTP/1.1" 200 -
request.url : http://127.0.0.1:10000/uri?key1=value1&key2=value2-1&key2=value2-2
uri_id_1 : None
request.query_string : b'key1=value1&key2=value2-1&key2=value2-2'
key :
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "GET /uri?key1=value1&key2=value2-1&key2=value2-2 HTTP/1.1" 200 -
request.method : POST
request.content_type : application/x-www-form-urlencoded
request.content_length : 9
request.is_json : False
request.form : ImmutableMultiDict([('key', 'value')])
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "POST /uri HTTP/1.1" 201 -
request.method : POST
request.content_type : application/json
request.content_length : 16
request.is_json : True
request.get_json() : {'key': 'value'}
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "POST /uri HTTP/1.1" 201 -
request.method : PUT
request.content_type : application/json
request.content_length : 40
request.is_json : True
request.get_json() : [['key1', 'value1'], ['key1', 'value2']]
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "PUT /uri HTTP/1.1" 200 -
request.method : PUT
request.content_type : application/json
request.content_length : 30
request.is_json : True
request.get_json() : {'key1': ['value1', 'value2']}
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "PUT /uri HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "DELETE /uri/id HTTP/1.1" 405 -
uri_id_2 : 123
request.remote_addr : 127.0.0.1
INFO:werkzeug:127.0.0.1 - - [19/Apr/2023 11:23:32] "DELETE /uri/123 HTTP/1.1" 204 -
-
클라이언트
$ python client.py
INFO:root:info log
WARNING:root:warning log
ERROR:root:error log
CRITICAL:root:critical log
----- status code ----- start
requests.codes.ok : 200
requests.codes.created : 201
requests.codes.no_content : 204
...
----- status code ----- end
----- options http://127.0.0.1:10000/index1 ----- start
request.status_code : 200
request.headers.get('allow') : HEAD, OPTIONS, GET
----- options http://127.0.0.1:10000/index1 ----- end
----- options http://127.0.0.1:10000/uri ----- start
request.status_code : 200
request.headers.get('allow') : PUT, OPTIONS, GET, HEAD, POST
----- options http://127.0.0.1:10000/uri ----- end
----- get http://127.0.0.1:10000/index1 ----- start
request.status_code : 200
request.headers : {'Server': 'Werkzeug/2.2.3 Python/3.9.16', 'Date': 'Wed, 19 Apr 2023 02:23:32 GMT', 'Content-Type': 'application/json', 'Content-Length': '28', 'Connection': 'close'}
request.headers['Content-Type'] : application/json
request.headers.get('content-type') : application/json
request.text : {
"result": "index2 ok"
}
request.encoding : utf-8
request.content : b'{\n "result": "index2 ok"\n}\n'
request.json() : {'result': 'index2 ok'}
request.history : [<Response [302]>]
----- get http://127.0.0.1:10000/index2 ----- end
----- head http://127.0.0.1:10000/uri ----- start
request.status_code : 200
request.headers : {'Server': 'Werkzeug/2.2.3 Python/3.9.16', 'Date': 'Wed, 19 Apr 2023 02:23:32 GMT', 'Content-Type': 'application/json', 'Content-Length': '21', 'Connection': 'close'}
----- head http://127.0.0.1:10000/uri ----- end
----- get http://127.0.0.1:10000/uri ----- start
request.status_code : 200
request.headers : {'Server': 'Werkzeug/2.2.3 Python/3.9.16', 'Date': 'Wed, 19 Apr 2023 02:23:32 GMT', 'Content-Type': 'application/json', 'Content-Length': '21', 'Connection': 'close'}
request.headers.get('content-type') : application/json
request.json() : {'key': 'value'}
----- get http://127.0.0.1:10000/uri ----- end
----- get http://127.0.0.1:10000/uri/id ----- start
request.status_code : 200
request.headers.get('content-type') : application/json
request.json() : {'key': 'value'}
----- get http://127.0.0.1:10000/uri/id ----- end
----- get http://127.0.0.1:10000/uri?key=value ----- start
request.status_code : 200
request.headers.get('content-type') : application/json
request.json() : {'key': 'value'}
----- get http://127.0.0.1:10000/uri?key=value ----- end
----- get http://127.0.0.1:10000/uri?key1=value1&key2=value2-1&key2=value2-2 ----- start
request.status_code : 200
----- get http://127.0.0.1:10000/uri?key1=value1&key2=value2-1&key2=value2-2 ----- end
----- post http://127.0.0.1:10000/uri ----- 1 start
request.status_code : 201
----- post http://127.0.0.1:10000/uri ----- 1 end
----- post http://127.0.0.1:10000/uri ----- 2 start
request.status_code : 201
----- post http://127.0.0.1:10000/uri ----- 2 end
----- put http://127.0.0.1:10000/uri ----- 1 start
request.status_code : 200
----- put http://127.0.0.1:10000/uri ----- 1 end
----- put http://127.0.0.1:10000/uri ----- 2 start
request.status_code : 200
----- put http://127.0.0.1:10000/uri ----- 2 end
----- delete http://127.0.0.1:10000/uri/id ----- 1 start
request.status_code : 405
request.headers.get('x-test') : None
request.json() : {'message': 'The method is not allowed for the requested URL.'}
----- delete http://127.0.0.1:10000/uri/id ----- 1 end
----- delete http://127.0.0.1:10000/uri/123 ----- 2 start
request.status_code : 204
request.headers.get('x-test') : value
----- delete http://127.0.0.1:10000/uri/123 ----- 2 end
