[Python] REST/HTTP API server/client
Updated:
REST
서버
- Flask, Flask-RESTX 이용
- Flask API Reference
- Flask-RESTX API Reference
- 설치
pip install Flask
pip 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
-
- 서버