この記事は、富士通クラウドテクノロジーズ Advent Calendar 2019の22日目の記事です。21日目は@kzmakeさんのGCRA(Generic cell rate algorithm)でAPIリクエストをレート制限する話でした。
はじめに
二フクラPaaSチームの@hunter9xです。今回はURI参照の一部に対する自分の理解をまとめてみようと思います。基本知識ですが、ネットで調べてみた際にあまり情報がなかったので書いてみようと思いました。
URIの基本
rfc3986#section-3ではURIは大体5つのパーツから構成されます。
foo://example.com:8042/over/there?name=ferret#nose
\_/ \______________/\_________/ \_________/ \__/
| | | | |
scheme authority path query fragment
| _____________________|__
/ \ / \
urn:example:animal:ferret:nose
更に//
から最初の/
までのauthority
部は下記のように構成されます。
authority = [ userinfo "@" ] host [ ":" port ]
様々なプロトコルやソフトウェアで上記で定義されたURI参照を用いてリソースのアクセスを実現しています。URI参照では、渡されたURIのすべてのパーツに沿って参照しなくても、現在の場所(base URI)からターゲットのリソース(target URI)にアクセスすることができます。例えば、https://pfs.nifcloud.com/service/dns.htm
にアクセスした後、https://pfs.nifcloud.com/service/ess.htm
が渡されたとすると、https://pfs.nifcloud.com/service/
までが同じなのでess.htm
に参照すれば良いです。他には #
などを用いた相対パスの参照があります。効率化のため、多くのプロトコルやソフトウェアではこのように柔軟にURIをパースしています。
相対URIの参照
さて、5つのパーツが揃っていないURIが渡された時にuri parserはどう動くべきかみて行きます。
詳細なアルゴリズムはrfc3986#section-5.2で示されており、かつパターンが非常に多いのでここでは、筆者自身が興味あるものだけを対象とします。
base uri | target uri | result |
---|---|---|
http://localhost:8080/hoge/ |
//localhost:8081 |
http://localhost:8081 |
http://localhost:8080/hoge/ |
/fuga |
http://localhost:8080/fuga |
http://localhost:8080/hoge/ |
fuga |
http://localhost:8080/hoge/fuga |
http://localhost:8080/hoge |
fuga |
http://localhost:8080/fuga |
検証
検証内容は、HTTPクライアントがHTTP サーバーに base uri でアクセスする時に、HTTPサーバー側で targe uri にリダイレクトする状況を作り、HTTPクライアント側の動きを確認してみたいと思います。検証で使うものは下記の通りです。
項目 | spec |
---|---|
HTTP サーバー | python 3.7.4 http.server module |
HTTP クライアント1 | php 7.2.24 file_get_contents()
|
HTTP クライアント2 | curl 7.58.0 (x86_64-pc-linux-gnu) |
準備
- 参照ディレクトリの構成
.
├── fuga
│ └── index.htm
└── hoge
└── index.htm
HTTPサーバ側 (python)
import http.server
import socketserver
PORT = 8080
BIND = "127.0.0.1"
target_uri = ""
class Redirect(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(301)
self.send_header("Location", target_uri)
self.end_headers()
with socketserver.TCPServer((BIND, PORT), Redirect) as httpd:
print("serving at port", PORT)
httpd.serve_forever()
すごく簡単なもので、無限にリダイレクトするようになっています。 target_uri
を変更しながら4パターンをテストします。目的はクライアント側の動きをみるので、そちらでリダイレクト回数を制限し、サーバーの標準出力でHTTPヘッダーで結果を確認します。
HTTP クライアント 1 (php)
<?php
$url = ""; // base uri
$option = [
"http" => [
'method' => 'GET',
'timeout' => 5,
'follow_location' => 1,
'max_redirects' => 2
]
];
file_get_contents($url, false, stream_context_create($option));
HTTP クライアント 2 (curl)
curl -v -L --max-redirs 2 {base uri}
結果
HTTP クライアントの base uri を変更し確認した結果になります。
sample num | base uri | target uri | curl | file_get_contents() |
---|---|---|---|---|
1 | http://localhost:8080/hoge/ |
//localhost:8081 |
http://localhost:8081 |
http://localhost:8081 |
2 | http://localhost:8080/hoge/ |
/fuga |
http://localhost:8080/fuga |
http://localhost:8080/fuga |
3 | http://localhost:8080/hoge/ |
fuga |
http://localhost:8080/hoge/fuga |
http://localhost:8080/hoge//fuga |
4 | http://localhost:8080/hoge |
fuga |
http://localhost:8080/fuga |
http://localhost:8080/fuga |
4パターンの中で、3番目だけ file_get_contents()
の動きが期待結果と違うことがわかります。一方 curl では期待どおりに動作していました。
終わりに
URI参照の基本について一部だけまとめました。実際2種類のHTTPクライアントの動作を確認し、参照アルゴリズムをみました。検証結果は正しいとは限らないのですが、URI参照の雰囲気を感じられたらなと思います。
明日は@imaisatoさんの災害は忘れたころにやって来るです。お楽しみに!