0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

リクエストヘッダーを追加するだけの簡易なAPIゲートウェイを作る

Posted at

サンプルAPIへリクエストする時にリクエストヘッダー1件を追加するだけの、簡易APIゲートウェイを自作してみる。

環境

  • macOS Ventura
  • Rancher Desktop 1.7.0 (Container Engine設定はdockerd)
  • Python3, Flask 2.2

サンプルAPIを作る

ディレクトリ

.
├── docker-compose.yml
└── helloapp
    ├── Dockerfile
    ├── app.py
    └── requirements.txt

ファイル

./docker-compose.yml
version: "3.0"
services:
  helloapp:
    build:
      context: helloapp
      target: builder
    stop_signal: SIGINT
    volumes:
      - ./helloapp:/app
    ports:
      - '10600:5000'
./helloapp/Dockerfile
# syntax=docker/dockerfile:1.4
FROM --platform=$BUILDPLATFORM python:3.11-alpine AS builder

WORKDIR /app

COPY requirements.txt /app
RUN --mount=type=cache,target=/root/.cache/pip \
    pip3 install -r requirements.txt

COPY . /app

CMD ["flask", "--app", "app.py", "--debug", "run", "--host", "0.0.0.0", "--port", "5000"]

# 調査用
# CMD ["tail", "-f"]
# docker exec -it <container id or name> /bin/sh
# flask --version
./helloapp/.requirements
flask==2.2.3
./helloapp/app.py
from flask import Flask, request, Response
import json
import random

app = Flask(__name__)

@app.route('/<path:path>', methods=['GET', 'POST'])
def hello(path):
	params = {}
	if request.method == 'GET':
		params = request.args
	elif request.method == 'POST':
		params = request.form
	
	j = json.dumps({
		'message': 'Hello world',
			'method': request.method,
			'path': path,
			'params': params,
			'headers': request.headers.to_wsgi_list(), 
	})

	s = random.randrange(400, 405, 1)
	
	return Response(response=j, status=s)

if __name__ == '__main__':
	app.run(host='0.0.0.0', port=8000)
./helloapp/.dockerignore
__pycache__

動作確認(1)

{"message": "Hello world", "method": "GET", "path": "foo/bar/hoge", "params": {"param1": "111", "param2": "222"}, "headers": [["Host", "localhost:10600"], ["Upgrade-Insecure-Requests", "1"], ["Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"], ["User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Safari/605.1.15"], ["Accept-Language", "ja"], ["Accept-Encoding", "gzip, deflate"], ["Connection", "keep-alive"]]}

簡易APIゲートウェイを作る

  • Flaskでgatewayを作る
  • gatewayはリクエストヘッダー Additional-Headerを追加して、helloapp のAPIにリクエストする
  • gatewayはhelloappのレスポンスを転記して、レスポンスする

ディレクトリ

  .
  ├── README.txt
  ├── docker-compose.yml
+ ├── gateway
+ │   ├── Dockerfile
+ │   ├── app.py
+ │   └── requirements.txt
  └── helloapp
      ├── Dockerfile
      ├── app.py
      └── requirements.txt

ファイル

docker-compose.yml を修正して  services.gatewayを追加。

./docker-compose.yml
version: "3.0"
services:
  helloapp:
    build:
      context: helloapp
      target: builder
    stop_signal: SIGINT
    volumes:
      - ./helloapp:/app
    ports:
      - '10600:5000'
        # ホストの http://localhost:10600/any-path
  gateway:
    build:
      context: gateway
      target: builder
    stop_signal: SIGINT
    volumes:
      - ./gateway:/app
    ports:
      - '10601:5000'
        # ホストの http://localhost:10601/any-path
./gateway/Dockerfile
# syntax=docker/dockerfile:1.4
FROM --platform=$BUILDPLATFORM python:3.11-alpine AS builder

WORKDIR /app

COPY requirements.txt /app
RUN --mount=type=cache,target=/root/.cache/pip \
    pip3 install -r requirements.txt

COPY . /app

CMD ["flask", "--app", "app.py", "--debug", "run", "--host", "0.0.0.0", "--port", "5000"]

# 調査用
# CMD ["tail", "-f"]
# docker exec -it <container id or name> /bin/sh
# flask --version
./gateway/.requirements
flask==2.2.3
requests
./gateway/app.py
from flask import Flask, request, Response
import requests

app = Flask(__name__)

@app.route('/<path:path>', methods=['GET', 'POST'])
def hello(path):
	# url
	url = 'http://host.docker.internal:10600/' + path
	
	# headers: copy
	reqHeaders = {}
	for k in request.headers.keys():
		reqHeaders[k] = request.headers[k]
	
	# headers: add
	reqHeaders["additional-header"] = "additional-header-value"

	# params/data
	params = {}
	if request.method == 'GET':
		params = request.args
		res = requests.get(url, params=params, headers=reqHeaders)
	elif request.method == 'POST':
		data = request.form
		res = requests.post(url, data=data, headers=reqHeaders)

	resHeaders = {}
	for k in res.headers.keys():
		resHeaders[k] = res.headers[k]
	
	return Response(response=res.text, headers=resHeaders, status=res.status_code)

if __name__ == '__main__':
	app.run(host='0.0.0.0', port=8000)
./gateway/.dockerignore
__pycache__

動作確認(2)

  • 上記のファイルを配置して docker-compose up --build する
  • ホストOSのブラウザなどで http://localhost:10601/foo/bar/hoge?param1=111&param2=222 を開く。
  • ホストOSのzshなどで、コマンドを実行する curl -X POST --data-urlencode 'name=太郎' -d 'age=30' http://localhost:10601/ababab/beef
ブラウザでアクセスした結果の例
{"message": "Hello world", "method": "GET", "path": "foo/bar/hoge", "params": {"param1": "111", "param2": "222"}, "headers": [["User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Safari/605.1.15"], ["Accept-Encoding", "gzip, deflate"], ["Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"], ["Connection", "keep-alive"], ["Host", "localhost:10601"], ["Upgrade-Insecure-Requests", "1"], ["Accept-Language", "ja"], ["Additional-Header", "additional-header-value"]]}
curlでPOSTした結果の例
{"message": "Hello world", "method": "POST", "path": "ababab/beef", "params": {"name": "\u592a\u90ce", "age": "30"}, "headers": [["User-Agent", "curl/7.86.0"], ["Accept-Encoding", "gzip, deflate"], ["Accept", "*/*"], ["Connection", "keep-alive"], ["Host", "localhost:10601"], ["Content-Length", "30"], ["Content-Type", "application/x-www-form-urlencoded"], ["Additional-Header", "additional-header-value"]]}%                          

参考リンク

docker/awesome-compose/flask
https://github.com/docker/awesome-compose/tree/master/flask

how to call another webservice api from flask
https://stackoverflow.com/questions/25149493/how-to-call-another-webservice-api-from-flask

(問題) webアプリをdockerで立ててアクセスしたらERR_EMPTY_RESPONSEエラーになった
(原因) containerの外からリクエストが来るのにアプリがlocalhostでLISTENしている
(解決) アプリの設定を0.0.0.0でLISTENするよう変更する
https://qiita.com/amuyikam/items/01a8c16e3ddbcc734a46

Auto reloading python Flask app upon code changes
https://stackoverflow.com/a/40150705

PythonのFlaskで階層自由なURLをルーティングする方法
https://lightgauge.net/language/python/flask-free-route-path

Docker コンテナ内からホストのポートにアクセスする方法まとめ
host.docker.internal
https://gotohayato.com/content/561/

python requests post リクエスト送信時に、header を設定する
https://www.monotalk.xyz/blog/python-requests-post-リクエスト送信時にheader-を設定する/

FlaskでシンプルにContent-Typeをチェックする(@content_type
https://qiita.com/5zm/items/c3f004291a87cdbce0b9

How do I set response headers in Flask?
https://stackoverflow.com/questions/25860304/how-do-i-set-response-headers-in-flask

flask Response
https://flask.palletsprojects.com/en/2.2.x/api/#flask.Response

flaskでhttpステータスを返却する方法
https://qiita.com/mink0212/items/52e0ebd66bd94e1303c1

The Request Object
https://flask.palletsprojects.com/en/2.2.x/quickstart/#the-request-object

curlコマンドでPOSTする, 様々な形式別メモ
https://weblabo.oscasierra.net/curl-post/

request.get()
https://www.w3schools.com/python/ref_requests_get.asp
requests.Response Object
https://www.w3schools.com/python/ref_requests_response.asp

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?