タイトルのとおりです。
(追記) 編集リクエストを受け、タイトルを若干修正しました。
Python3であることを明記しました。
記事を最初に書いた時の勘違いが激しかったので、再構成しました。
環境
以下の二つの環境で試してみました。
Cygwin on windows7
- gcc, gfortran 7.3.0
- python 3.6.4
- perl v5.26.2
Ubuntu 16.04
- gcc, gfortran 5.4.0
- python 3.5.2
- perl 5.22.1
- php 7.0.30
準備
まず、pythonでSimpleHTTserverを立ち上げる作業ディレクトリを作り、そこに移動します。
mkdir python_cgi_test
cd python_cgi_test
この中に、cgi-bin
ディレクトリとindex.html
を作っておきます。
mkdir cgi-bin
vi index.html
<!DOCTYPE html>
<html>
<head>
<title>Python Simple HTTP server</title>
</head>
<body>
<ul>
<li> <a href="/cgi-bin/test_py">Python</a> </li>
<li> <a href="/cgi-bin/test_pl">Perl</a> </li>
<li> <a href="/cgi-bin/test_php?hoge=fuga">PHP</a> </li>
<li> <a href="/cgi-bin/test_c">C</a> </li>
<li> <a href="/cgi-bin/test_f90">Fortran</a> </li>
</ul>
</body>
</html>
このindex.html
があるフォルダに行き、
python -m http.server --cgi
として、http://localhost:8000/ にアクセスするとindex.html
がブラウザで出力されます。ここには各言語へのCGIテストに行くリンクも用意しました。
python
#!/usr/bin/python
print("Content-type: text/html")
print("")
print("<html>")
print("Hello,World!")
print("</html>")
まあ、ググったらすぐに見つかるサンプルです。
以降、出力結果はブラウザで見ると<html>
か<body>
で囲まれた部分の挨拶的な文字列が出るだけなので省略します。
perl
#!/usr/bin/perl
print "Content-type: text/html\n\n";
print "<html><body>\n";
print "Success CGI!\n";
print "</body></html>";
exit;
ほかのインタプリタ言語についても、シバンに書いてやれば対応できるようです。
ちなみに、余りに基本的な構文すぎるので、perlではなくpython2でも問題なく実行できてしまいます。python3にするとさすがに「SyntaxError: Missing parentheses in call to 'print'. 」になります。
C
コンパイル言語だってCGIに使えます。
#include <stdio.h>
int main(void)
{
printf("Content-type: text/html\n\n");
printf("<HTML><BODY>");
printf("Hello from C");
printf("</BODY></HTML>");
printf("\n");
return 0;
}
gcc test.c -o test_c
mv test_c /path/to/python_server_test/cgi-bin
Cygwinだとtest_c.exeファイルになります。適当にリネームしましょう。
要は適切な内容を標準出力に出す実行ファイルを作ってやれば良いのです。
Fortran
Cと同様にFortranでもできます。
program cgi_test
print '(A)', "Content-type: text/html"
print '(A)', ""
write(*, *)"<html>"
write(*, *)"Hello from fortran"
write(*, *)"</html>"
stop
end program cgi_test
gfortran test.f90 -o test_f90
mv test_f90 /path/to/python_server_test/cgi-bin
最初2つの出力がprint
なのは、write(*,*)
だとContent ...
の前にスペースが1つついてしまい、CGIの出力として解釈されなかったためです。
これで、我らがFortranでCGIを書き、それを簡単にテスト実行できるかもしれないというめどが立ってきました。
ディレクトリ構造
python_server_test
├── cgi-bin
│ ├── test_c
│ ├── test_f90
│ ├── test_pl
│ └── test_py
├── cgi-source
│ ├── test.c
│ └── test.f90
└── index.html
cd python_server_test
python -m http.server --cgi
POST/GET
CGIとなると、やはりブラウザからの入力を受け付けたいものです。ブラウザからのリクエストとして、まずPOSTとGETを確かめてみます。
Cookieなどは気が向いたらいずれ。
html側
リクエストを発生させるため、htmlファイルに以下のフォームを作成します。
<form method="POST" action="/cgi-bin/hogehoge?foo=bar">
<input type="text" name="hoge1" value="fuga">
<input type="submit" name="submit_button" value="press">
</form>
cgi-bin
の下にテストしたい実行ファイルをhogehoge
としてリネームして置いてやって、"press"ボタンを押します。
php
現状の僕にとって、サーバーからのデータを確かめるコードを一番書きやすかったのがphpなので、まずはこれで書いてみます。
#!/usr/bin/php
Content-type: text/html
<html>
<body>
<h1> $_GET </h1>
<pre> <?php var_dump($_GET); ?> </pre>
<h1> $_POST </h1>
<pre> <?php var_dump($_POST); ?> </pre>
<h1> GET(QUERY_STRING) </h1>
<p><?php echo $_SERVER["QUERY_STRING"]; ?></p>
<h1> POST(stdin) </h1>
<p><?php
if( "POST" === $_SERVER["REQUEST_METHOD"] ){
$length = intval($_SERVER["CONTENT_LENGTH"]);
$post = fgets(STDIN, $length + 1);
echo $post;
}else{
echo "No POST data found";
}
?></p>
</body>
</html>
Apacheなどから普通にphpを叩くと、GETは$_GET
配列に、POSTは$_POST
に入っています。ところが、上のコードのようにとりあえずvar_dump
で中身を見てみても、空配列になっています。
ではどこにあるかと言いますと、GETは環境変数のQUERY_STRING
、POSTは標準入力から与えられます。
ということで、GETはecho $_SERVER["QUERY_STRING"];
で、
POSTは$length = intval($_SERVER["CONTENT_LENGTH"]);
で文字列長を得てから$post = fgets(STDIN, $length+1);
と取得します。
ここで$post = fgets(STDIN);
と長さ不定で取ろうとすると、EOLを待ち続けるためなのかわかりませんが、フリーズしてしまいます。
一応実行結果例を書いておきます。
$_GET
array(0) {
}
$_POST
array(0) {
}
GET(QUERY_STRING)
foo=bar
POST(stdin)
hoge1=fuga&submit_button=press
ちなみにfilter_input
は変数が設定されていない場合の挙動を示しました。
調べてないので想像ですが、$_GET
と$_POST
は、apacheなどのHTTPサーバーのモジュールとしてPHPが実行した時に定義されるのでしょうか。今回はどうもローカル上のインタプリタとして動いているようですので。
これをpythonサーバーでやろうとするとサーバー側で何らかのコードを書かないと行けないのでしょうか。
C
コメントで@tenmyoさんが書かれた通りです。ありがとうございました。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[], const char *envp[])
{
size_t i=0;
printf("Content-type: text/plain\r\n\r\n");
// ENV
for (i=0; envp[i]; ++i) {
printf("%s\n", envp[i]);
}
printf("----\n");
// STDIN
const char *method = getenv("REQUEST_METHOD");
if (method) {
if (!strcmp(method, "POST")) {
const char *len = getenv("CONTENT_LENGTH");
size_t size = 0;
if (len) {
size = atoll(len);
}
unsigned char *buf = malloc(size);
if (!buf) {
return -1;
}
size = fread(buf, 1, size, stdin);
fwrite(buf, 1, size, stdout);
}
}
return 0;
}
これは実行すると環境変数がすべて出力されますので、適切に応用してやれば良いかなと思います。
Fortran
これがやりたかった。
まず、環境変数の取得ですが、get_environment_variable
を2回呼ぶのが正攻法かと思います。
character(:), allocatable :: get
integer :: length, status
intrinsic :: get_environment_variable
character(:), parameter :: env_query = "QUERY_STRING"
call get_environment_variable(env_query, status=status, length=length)
if (status == 0)then
allocate(character(length) :: get)
call get_environment_variable(env_query, value=get)
write(*, *) "<h1>GET</h1>"
write(*, *) get
deallocate(get)
endif
1回目のcallで文字列を取得し、バッファ用文字列をallocateし、2回目のcallでようやく環境変数を取得します。deferred length characterの挙動のため、若干冗長な書き方になってしまっています。
続いてPOSTの取得です。
REQUEST_METHOD
がPOST
だったら、文字列長を取得し、
call get_environment_variable("CONTENT_LENGTH", value=clen)
clen
はcharacter
型なので整数型に変換し、
read(clen, *) length
標準入力から取得します。
allocate(character(length) :: post)
read(*, '(A)', advance='no', size=length) post
ここでもphpでのfgets()
の話と同様に、文字列長を固定してやる必要があります。
read(*,*) post
だとフリーズします。
以上をちゃんと動く形でまとめたものが以下になります。
program test_getpost
implicit none
character(:), allocatable :: method, get, post, clen
integer :: length, status
intrinsic :: get_environment_variable
character(:), parameter :: env_query = "QUERY_STRING"
character(:), parameter :: env_method = "REQUEST_METHOD"
character(:), parameter :: env_length = "CONTENT_LENGTH"
print '(A)', "Content-type: text/html"
print '(A)', ""
write(*, *)"<html>"
write(*, *) "<head>"
write(*, *) "<title>GET POST with fortran</title>"
write(*, *) "</head>"
write(*, *) "<body>"
call get_environment_variable(env_query, status=status, length=length)
if (status == 0)then
allocate(character(length) :: get)
call get_environment_variable(env_query, value=get)
write(*, *) "<h1>GET</h1>"
write(*, *) get
deallocate(get)
endif
call get_environment_variable(env_method, status=status, length=length)
if(status == 0) then
allocate(character(length) :: method)
call get_environment_variable(env_method, value=method)
if (method == "POST") then
call get_environment_variable(env_length, status=status, length=length)
allocate(character(length) :: clen)
call get_environment_variable(env_length, value=clen)
read(clen, *) length
allocate(character(length) :: post)
write(*, *) "<h1>POST</h1>"
read(*, '(A)', advance='no', size=length) post
write(*, *) "<p>" // post // "</p>"
deallocate(post)
endif
deallocate(method)
endif
write(*, *) "</body>"
write(*, *)"</html>"
stop
end program test_getpost
これで我らがFortranでもCGIを安心して書けますね。
注意
Pythonのsimplehttpの仕様として、CGI実行ファイルは実行権限を付けて cgi-bin
ディレクトリ以下におく必要があるようです。他のディレクトリ名にするとプレーンテキストとしてそのまま表示されました。
https://docs.python.org/3/library/http.server.html#http.server.CGIHTTPRequestHandler
によると、cgi_directories
に['/cgi-bin', '/htbin']
として定義されているそうです。
他のディレクトリに置きたい場合は、たぶん自分でpythonスクリプトを書く必要があるかと思います。
お詫び
この記事を最初書いた時は、python -m http.server --cgi
ではPOST/GETが取得できないという話をしていました。
これは単純に、僕の勘違いから取得に失敗していただけでした。
みなさんの指摘をもとに修正したらうまく行きましたので、それを元に記事を再構成しました。
@shiracamusさん、@tenmyoさん、ありがとうございました。
記事を修正したためコメントが若干話がつながらないかもしれませんが、残しておきます。
言い訳
- GET:phpなら
$_GET
にあると思い込んでおりました。今作ってるWordpressプラグインではGET使わないし・・・これを機会にこういうスーパーグローバル変数依存はやめようかなと。 - POST:Fortranの
read(*,*)
でフリーズしましたので、ダメなのかと思いました。