質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.12%

pythonCGIからjsonを返したい(クロスオリジン要求の解決)

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 743

meron-pan

score 35

 前提・実現したいこと

pythonでcgiを作ってみたいと思い、実験としてajaxを使ってjavascriptでjsonファイルを送信し、それをオウム返しするcgiを作りました。
返ってきたデータをコンソールで確認しようとしたところエラーが発生しましたが、解決方法がわかりません。
使っているのは、python3.7と、javaScript、html5です。(開発環境はwindows(Python3.7インストール済み)、ブラウザはfireFoxで検証)
ローカルサーバーを立ち上げて実験しています。

ファイル構成
server/
├ cgi-bin/
│ └  server.py
├cgiserver
├ajax.js
└ index.html

 発生している問題・エラーメッセージ

クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、http://127.0.0.1:8000/cgi-bin/server.py にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Origin’ が足りない)。[詳細]
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、http://127.0.0.1:8000/cgi-bin/server.py にあるリモートリソースの読み込みは拒否されます (理由: CORS 要求が成功しなかった)。[詳細]

 該当のソースコード

cgiserver.py

# -*- coding: utf-8 -*-

import http.server

http.server.test(HandlerClass = http.server.CGIHTTPRequestHandler)


server.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import io
import os
import sys
import cgi
import cgitb
import urllib.request
import json

sys.stdin = io.TextIOWrapper(sys.stdin.buffer,encoding='utf-8')
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

cgitb.enable()

if os.environ['REQUEST_METHOD'] == 'POST':
   form = cgi.FieldStorage()
   print("Access-Control-Allow-Origin: *\n")
   print('Content-Type: text/json; charset=utf-8\r\n')
   print(form)


ajax.js

ajaxSend = function (json) {
    console.log("a")
    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'http://127.0.0.1:8000/cgi-bin/server.py', true);
    xhr.setRequestHeader('content-type', 'application/json');
    xhr.send(json);
    ajaxGet();
}

ajaxGet = function () {
    var req = new XMLHttpRequest();
    try{
        if (httpRequest.readyState === XMLHttpRequest.DONE) {
            if (httpRequest.status === 200) {
              alert(req.responseText);
            }else {
                alert('リクエストに問題が発生しました');
            }
        }
    }
    catch(e){
        alert('例外を補足:'+e.description);
    }
}

json = {
    "messages":
        [
            {
                "date": "2012/1/12 19:12",
                "text": "こんばんは"
            }
            ,
            {
                "date": "2012/1/11 19:12",
                "text": "Hello"
            }
        ]
}

window.onload = function(){
    console.log(document.getElementById("ajaxButton"))
    document.getElementById("ajaxButton").addEventListener('click',a)
}

a = function(){
    ajaxSend(json);
}


index.html

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>Javascript試験用</title>
    <script charset="UTF-8" type="text/javascript" src="ajax.js"></script>
</head>

<body>
        <button id="ajaxButton" type="button">決定</button>
</body>
</html>
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • meron-pan

    2018/10/08 20:43

    パスや関連付けの場合そもそもpythonが動きませんし、cgiからデータが返ってこなくなる(これも返って来ているのかは断定できませんが)ので問題はないと思うのですが、関連付けができていない場合でもpythonを動かすことは可能なのでしょうか?

    キャンセル

  • wwbQzhMkhhgEmhU

    2018/10/09 00:46

    返事があるとは思ってませんでした。どこから話せばいいのかよく分かりませんが、まずはajaxでない普通のhello, worldレベルのhtmlを返すところから確認した方がいいと思います。ぶっちゃけ*.pyが何をキックしてるのか不明だし、そもそもhttpサーバも何か分からないし、サーバー側のserver.py確認したのか分からないし、クライアント側でデバッガ使えるのかも不明だし、そんなこんなを1つずつ聞く手間が面倒になってああ書きました。

    キャンセル

  • wwbQzhMkhhgEmhU

    2018/10/09 00:49

    後は自力でお願いします。

    キャンセル

回答 2

checkベストアンサー

0

上記のプログラムですが、私のローカル環境でも動かしてみました。
おそらく、1箇所だけではなく、数カ所に問題があるようです。

 1点目

まず、現在出ているエラーですが、CORSが原因で出ていますので、この問題を解決する必要があります。
すでに調べられたかとは思いますが、CORSが原因のエラーは、JavaScriptが埋め込まれているドメイン(URL)とは別のURLにアクセスしようとした時に、セキュリティ上の理由で発生するエラーです。

参考: CORSまとめ - Qiita

おそらくindex.htmlの画面の動作確認を https://localhost:8000 で、されたのではないでしょうか。
この時、JavaScriptが http://127.0.0.1:8000 配下のURLにアクセスしようとすると、文字列上は、localhostと127.0.0.1で(ブラウザとしては)別ドメインと認識されてしまうため、エラーが発生します。

今回は、PythonでCGIを作る事が目的のようなので、ajax.jsで、書かれている以下の箇所は、

xhr.open('POST', 'http://127.0.0.1:8000/cgi-bin/server.py', true);

ではなく、一旦、

xhr.open('POST', '/cgi-bin/server.py', true);

で良いかと思われます。(それ以外の方法として、http://127.0.0.1:8000 でindex.htmlにアクセスして動作確認する、CORS用の設定をするなどありますが、割愛します。)

この方法で、本件の質問に記載されているエラーは発生しなくなるはずです。

 2点目

次にserver.pyのコードですが、こちらにも問題があります。

server.pyの以下のコードですが、

form = cgi.FieldStorage()

今回は、JSONでアクセスされるため、cgi.FieldStorage()ではJSONを正しく読み込めないはずです。cgi.FieldStorage()は、どうも、HTMLフォームをPOSTした時に使用する関数のようです。Python側でエラーが発生しており、リクエストが来ても正しく処理できていないと思われます。

調べてみましたが、PythonのCGIでJSONを受け取ってそのまま返す時は、Pythonの標準入力から読み込むのが一般的なようですね。

参考までに私が書いてみたものを載せておきます。これは、文字列を受け取って文字列をそのまま返すだけの処理ですが、JSONもまた、ただの文字列なので、以下のような感じでechoサーバーが書けました。

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import os
import sys
import cgi

if os.environ['REQUEST_METHOD'] == 'POST':
    length, _ = cgi.parse_header(os.environ['CONTENT_LENGTH'])
    data = sys.stdin.buffer.read(int(length))
    print('Content-Type: text/json; charset=utf-8')
    print("Access-Control-Allow-Origin: *\r\n")
    print(data.decode("utf-8"))

CONTENT_LENGTH(リクエストの文字列の長さ)を取得して、その文字数分だけ、標準入力から読みこみ、その結果にヘッダを付けてそのまま投げ返しているだけです。(ちなみに、JSONとして読み込む場合は、JSONのパース処理が必要になります。)

ちなみに、JSONを返すAPIなどを作成される場合は、ブラウザでJavaScriptを実行してアクセスして動作確認よりもでAPI単体(この場合は、URLにPOSTで直接アクセスして)で動作確認した方が、問題の切り分けがしやすくなります。(JavaScriptが間違ってるのか、Pythonのコードがおかしいのか判断しやすくなります。)

API単体で動作確認するためのツールが色々あるので、使いやすそうなものを探して、使ってみて下さい。参考までに2点ほど上げておきます。(もちろん、curlコマンドで叩くというのもアリです。 curlコマンドを使ってHTTPSのAPIを叩く - Qiita )

 3点目

ajax.jsに戻りますが、URLだけでなく、それ以外の箇所も(正常に動作させるなら)修正した方が良さそうです。この箇所に関しては、全てを説明するのは、非常に大変なので、割愛させていただいたいですが、要約すると、以下二点になります。

  • JavaScriptの非同期通信の処理の方法について調べられたらよいかと思われます。
  • fetchという割と便利な関数があるので、(古いブラウザで動かしたいというわけでなければ、)おすすめします。

参考までに以下のコードを載せておきます。

json = {
    "messages":
        [
            {
                "date": "2012/1/12 19:12",
                "text": "こんばんわ"
            }
            ,
            {
                "date": "2012/1/11 19:12",
                "text": "Hello"
            }
        ]
}

ajaxSend = function (json) {
    var url = "/cgi-bin/server.py";
    var method = "POST";
    var headers = {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
    };
    var body = JSON.stringify(json);
    var mhb = {method, headers, body}
    fetch(url, mhb)
        .then(function(response) {
            return response.json();
        }).then(function(responseJson) {
            alert(JSON.stringify(responseJson));
        }).catch(console.error);
}

window.onload = function(){
    console.log(document.getElementById("ajaxButton"))
    document.getElementById("ajaxButton").addEventListener('click',a)
}

a = function(){
    ajaxSend(json);
}

長文になってしまいましたが、少しでも理解の一助となれば幸いです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/24 15:05

    回答ありがとうございます。pythonの標準モジュールのhttp.serverだと、どうやらcgi-binフォルダをサーバーにするようで、htmlファイルを同じ階層にすれば動きました。まさか、ファイルの位置で、URLが異なってしまうとは思わなかったです。cgi.FieldStorage()では、JSONを受け取れないのは、初めて知りました。
    なんとか、Javascriptの方はwebにいろいろ情報が載っていたので、自力で修正できたんですが、pythonについては情報が少なかったのでとても助かりました。ありがとうございます。

    キャンセル

0

postされたjsonの値をcgi(python)で取得する場合
例)ajax,json形式でpostされた項目id,passwordとすると

import sys
import json

data = sys.stdin.read()
params = json.load(data)
id = params['id']
password = params['password']

で取得できます。

結果をjson形式で返すばあいは (resultsをjson形式で返す場合)
print("Content-type: application/json")
print("\n\n")
jsonstring = json.dumps(results)
print(jsonstring)
print('\n')

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる