web-dev-qa-db-ja.com

Python)でWebプロキシを作成する方法

PythonでWebプロキシを作成しようとしています。目標は、http://proxyurl/http://anothersite.com/のようなURLにアクセスして、通常どおりにhttp://anothersite.comの内容を確認することです。私はリクエストライブラリを悪用することでかなり遠くまで行きましたが、これは実際にはリクエストフレームワークの意図された使用法ではありません。私は以前に twisted でプロキシを作成しましたが、これを私がやろうとしていることに接続する方法がわかりません。これが私が今のところいるところです...

import os
import urlparse

import requests

import tornado.ioloop
import tornado.web
from tornado import template

ROOT = os.path.dirname(os.path.abspath(__file__))
path = lambda *a: os.path.join(ROOT, *a)

loader = template.Loader(path(ROOT, 'templates'))


class ProxyHandler(tornado.web.RequestHandler):
    def get(self, slug):
        if slug.startswith("http://") or slug.startswith("https://"):
            if self.get_argument("start", None) == "true":
                parsed = urlparse.urlparse(slug)
                self.set_cookie("scheme", value=parsed.scheme)
                self.set_cookie("netloc", value=parsed.netloc)
                self.set_cookie("urlpath", value=parsed.path)
            #external resource
            else:
                response = requests.get(slug)
                headers = response.headers
                if 'content-type' in headers:
                    self.set_header('Content-type', headers['content-type'])
                if 'length' in headers:
                    self.set_header('length', headers['length'])
                for block in response.iter_content(1024):
                    self.write(block)
                self.finish()
                return
        else:
            #absolute
            if slug.startswith('/'):
                slug = "{scheme}://{netloc}{original_slug}".format(
                    scheme=self.get_cookie('scheme'),
                    netloc=self.get_cookie('netloc'),
                    original_slug=slug,
                )
            #relative
            else:
                slug = "{scheme}://{netloc}{path}{original_slug}".format(
                    scheme=self.get_cookie('scheme'),
                    netloc=self.get_cookie('netloc'),
                    path=self.get_cookie('urlpath'),
                    original_slug=slug,
                )
        response = requests.get(slug)
        #get the headers
        headers = response.headers
        #get doctype
        doctype = None
        if '<!doctype' in response.content.lower()[:9]:
            doctype = response.content[:response.content.find('>')+1]
        if 'content-type' in headers:
           self.set_header('Content-type', headers['content-type'])
        if 'length' in headers:
            self.set_header('length', headers['length'])
        self.write(response.content)


application = tornado.web.Application([
    (r"/(.+)", ProxyHandler),
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

注意点として、クエリ文字列にstart = trueがある場合は、scheme、netloc、およびurlpathを保持するようにCookieを設定します。そうすれば、プロキシに到達する相対リンクまたは絶対リンクは、そのCookieを使用して完全なURLを解決します。

このコードでは、http://localhost:8888/http://espn.com/?start=trueに移動すると、ESPNの内容が表示されます。ただし、次のサイトではまったく機能しません: http://www.bottegaveneta.com/us/shop/ 。私の質問は、これを行うための最良の方法は何ですか?私がこれを実装している現在の方法は堅牢ですか、それともこの方法でそれを行うことにはいくつかのひどい落とし穴がありますか?それが正しければ、私が指摘したような特定のサイトがまったく機能しないのはなぜですか?

助けてくれてありがとう。

21
Kang Roodle

私は最近、同様のWebアプリケーションを作成しました。これが私がやった方法であることに注意してください。私はあなたがこのようにすべきだと言っているのではありません。これらは私が遭遇した落とし穴のいくつかです:

絶対値から相対値への属性値の変更

単にページをフェッチしてクライアントに提示するだけではなく、はるかに複雑なことがあります。多くの場合、エラーなしでWebページをプロキシすることはできません。

私が指摘したような特定のサイトがまったく機能しないのはなぜですか?

多くのWebページは、適切にフォーマットされた方法でWebページを表示するために、リソースへの相対パスに依存しています。たとえば、次の画像タグ:

<img src="/header.png" />

クライアントは次のリクエストを実行します。

http://proxyurl/header.png

どちらが失敗します。 'src'値は次のように変換する必要があります。

http://anothersite.com/header.png.

したがって、HTMLドキュメントを BeautifulSoup のようなもので解析し、すべてのタグをループして、次のような属性を確認する必要があります。 :

'src', 'lowsrc', 'href'

そしてそれに応じて値を変更し、タグが次のようになるようにします。

<img src="http://anothersite.com/header.png" />

この方法は、画像だけでなく、より多くのタグに適用されます。 ascriptリンクliおよびframeはいくつか変更する必要があります。

HTML shenanigans

前の方法でうまくいくはずですが、まだ完了していません。

両方

<style type="text/css" media="all">@import "/stylesheet.css?version=120215094129002";</style>

そして

<div style="position:absolute;right:8px;background-image:url('/Portals/_default/Skins/BE/images/top_img.gif');height:200px;width:427px;background-repeat:no-repeat;background-position:right top;" >

BeautifulSoup を使用して到達および変更するのが難しいコードの例です。

最初の例では、相対URIへのcss @ Importがあります。 2つ目は、インラインCSSステートメントの 'url()'メソッドに関するものです。

私の状況では、これらの値を手動で変更するための恐ろしいコードを書くことになりました。これには正規表現を使用することをお勧めしますが、よくわかりません。

リダイレクト

Python-RequestsまたはUrllib2を使用すると、リダイレクトを自動的に簡単にたどることができます。新しい(ベース)URIが何であるかを保存することを忘れないでください。 '属性値を相対から絶対に変更する'操作に必要になります。

また、「ハードコードされた」リダイレクトを処理する必要があります。このような:

<meta http-equiv="refresh" content="0;url=http://new-website.com/">

次のように変更する必要があります。

<meta http-equiv="refresh" content="0;url=http://proxyurl/http://new-website.com/">

ベースタグ

ベースタグ は、ドキュメント内のすべての相対URLのベースURL /ターゲットを指定します。おそらく値を変更したいと思うでしょう。

最後に完了しましたか?

いいえ。一部のWebサイトは、コンテンツを画面に描画するためにJavaScriptに大きく依存しています。これらのサイトはプロキシするのが最も難しいです。 PhantomJSGhost のようなものを使用して、Webページをフェッチして評価し、その結果をクライアントに提示することを考えていました。

多分私の ソースコード はあなたを助けることができます。好きなように使えます。

7
cpb2

実際のプロキシを作成する場合は、次を使用できます。

竜巻プロキシ

または

Twistedに基づく単純なプロキシ

しかし、あなたのケースにそれらを適応させるのは難しいことではないと思います。

3
shirk3y

標準ライブラリのソケットモジュールを使用できます。Linuxepollを使用している場合も同様です。

単純な非同期サーバーのサンプルコードは次の場所にあります: https://github.com/aychedee/octopus/blob/master/octopus/server.py

0
aychedee

最後のifブロックは必要ないと思います。これは私にとってはうまくいくようです:

class ProxyHandler(tornado.web.RequestHandler):
    def get(self, slug):
        print 'get: ' + str(slug)

        if slug.startswith("http://") or slug.startswith("https://"):
            if self.get_argument("start", None) == "true":
                parsed = urlparse.urlparse(slug)
                self.set_cookie("scheme", value=parsed.scheme)
                self.set_cookie("netloc", value=parsed.netloc)
                self.set_cookie("urlpath", value=parsed.path)
            #external resource
            else:
                response = requests.get(slug)
                headers = response.headers
                if 'content-type' in headers:
                    self.set_header('Content-type', headers['content-type'])
                if 'length' in headers:
                    self.set_header('length', headers['length'])
                for block in response.iter_content(1024):
                    self.write(block)
                self.finish()
                return
        else:

            slug = "{scheme}://{netloc}/{original_slug}".format(
                scheme=self.get_cookie('scheme'),
                netloc=self.get_cookie('netloc'),
                original_slug=slug,
            )
            print self.get_cookie('scheme')
            print self.get_cookie('netloc')
            print self.get_cookie('urlpath')
            print slug
        response = requests.get(slug)
        #get the headers
        headers = response.headers
        #get doctype
        doctype = None
        if '<!doctype' in response.content.lower()[:9]:
            doctype = response.content[:response.content.find('>')+1]
        if 'content-type' in headers:
           self.set_header('Content-type', headers['content-type'])
        if 'length' in headers:
            self.set_header('length', headers['length'])
        self.write(response.content)
0
andy boot

どうやら私はこれに答えるのにかなり遅れているようですが、しばらく前にそれを偶然見つけました。私はあなたの要件に似たものを自分で書いています。

これはHTTPリピーターのようなものですが、最初のタスクはプロキシ自体です。それはまだ完全ではなく、今のところ私を読んでいません-しかし、それらは私のやることリストにあります。

私はこれを達成するためにmitmproxyを使用しました。それは世の中で最もエレガントなコードではないかもしれません、そして私はリピーター機能を達成するためにあちこちでたくさんのハックを使いました。 mitmproxyにはデフォルトでリピーターを簡単に実現する方法があることは知っていますが、私の場合、mitmproxyが提供する機能を使用できないという特定の要件がありました。

プロジェクトは https://github.com/c0n71nu3/python_repeater/ で見つけることができます。開発が行われると、リポジトリはまだ更新中です。

うまくいけば、それはあなたにいくつかの助けを提供することができるでしょう。

0
qre0ct