2013-05-13 23 views
20

Sto provando a scrivere un proxy Web in python. L'obiettivo è di visitare un URL come: http://proxyurl/http://anothersite.com/ e vedere il contenuto di http://anothersite.com proprio come faresti normalmente. Sono arrivato decentemente lontano abusando della libreria delle richieste, ma questo non è proprio l'uso previsto del framework delle richieste. Ho già scritto dei proxy con twisted, ma non sono sicuro di come collegarlo a ciò che sto cercando di fare. Ecco dove sono a finora ...Come scrivere un proxy Web in Python

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() 

Solo una nota, ho impostare un cookie per conservare lo schema, netloc, e URLPath se c'è start = true nella querystring. In questo modo, qualsiasi collegamento relativo o assoluto che colpisce il proxy utilizza quel cookie per risolvere l'url completo.

Con questo codice, se si passa a http://localhost:8888/http://espn.com/?start=true vedrete il contenuto di ESPN. Tuttavia, nel seguente sito non funziona affatto: http://www.bottegaveneta.com/us/shop/. La mia domanda è, qual è il modo migliore per farlo? È il modo attuale in cui sto implementando questo robusto o ci sono alcuni trabocchetti terribili a farlo in questo modo? Se è corretto, perché alcuni siti come quello che ho segnalato non funzionano affatto?

Grazie per qualsiasi aiuto.

+0

Bottega Veneta non consente di accedere direttamente alle risorse. Ad esempio, prova a premere http://www.bottegaveneta.com/us/shop/css/bottegaveneta/form.css - Ricevo una pagina HTML 404. –

+2

Immagino che abbia a che fare con il Referrer HTTP. Puoi provare a impostare anche quello. –

+0

@Cole Oh, intendi il referente? (https://en.wikipedia.org/wiki/HTTP_referer#Origin_of_the_term_referer) – rakslice

risposta

0

Penso che tu non abbia bisogno del tuo ultimo blocco. Questo sembra funzionare per me:.

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) 
-3

è possibile le richieste degli utenti modulo

import requests 

proxies = { 
    "http": "http://10.10.1.10:3128", 
    "https": "http://10.10.1.10:1080", 
} 

requests.get("http://example.org", proxies=proxies) 

request docs

+0

perché non +1 o più? – sinceq

+8

perché sta provando a * scrivere * un proxy, non * usare * uno – Xavier

7

Ho recentemente scritto una simile web-application. Si noti che questo è il modo in cui l'ho fatto. Non sto dicendo che dovresti farlo in questo modo. Queste sono alcune delle insidie ​​mi sono imbattuto:

Modifica dei valori degli attributi da relativo ad assoluto

C'è molto più coinvolto rispetto al semplice recupero di una pagina e la presentazione al cliente. Molte volte non sei in grado di eseguire il proxy della pagina web senza errori.

Perché alcuni siti come quello che ho segnalato non funzionano affatto?

Molte pagine web si basano su percorsi relativi alle risorse, al fine di visualizzare la pagina Web in un modo ben formattato.Ad esempio, questo tag immagine:

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

comporterà il cliente a fare una richiesta a:

http://proxyurl/header.png 

che non riesce. Il valore 'src' dovrebbe essere convertito:

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

Quindi, è necessario analizzare il documento HTML con qualcosa di simile BeautifulSoup, ciclo su tutti i tag e verificare la presenza di attributi quali:

'src', 'lowsrc', 'href' 

E modificarne i valori di conseguenza in modo che il tag diventa:

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

Questo metodo si applica a più tag rispetto a quello dell'immagine. un, sceneggiatura, collegamento, Li e telaio sono alcuni si dovrebbe cambiare.

imbrogli HTML

Il metodo preventiva dovrebbero arrivare lontano, ma non abbiamo ancora finito.

Sia

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

E

<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;" > 

sono esempi di codice che è difficile da raggiungere e modificare usando BeautifulSoup.

Nel primo esempio c'è un css @Import su un uri relativo. Il secondo riguarda il metodo 'url()' da un'istruzione CSS in linea.

Nella mia situazione, ho finito per scrivere codice orribile per modificare manualmente questi valori. Potresti voler usare Regex per questo, ma non ne sono sicuro.

Reindirizza

con Python-Richieste o urllib2 si può facilmente seguire reindirizza automaticamente. Ricordati solo di salvare ciò che il nuovo (base) uri è; ne avrai bisogno per la "modifica dei valori degli attributi da un'operazione relativa ad assoluta".

È inoltre necessario gestire i reindirizzamenti "hardcoded".Come ad esempio questo:

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

deve essere cambiato in:

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

tag base

Il base tag Specifica la base URL/target per tutti gli URL relativi in ​​un documento. Probabilmente vuoi cambiare il valore.

Finalmente completato?

No. Alcuni siti Web si basano molto su javascript per disegnare i loro contenuti sullo schermo. Questi siti sono i più difficili da proxy. Stavo pensando di usare qualcosa come PhantomJS o Ghost per recuperare e valutare pagine web e presentare il risultato al cliente.

Forse il mio source code può aiutarti. Puoi usarlo nel modo che preferisci.

+3

È possibile inserire un tag '' nell'intestazione del documento per correggere gli URL relativi in ​​un colpo solo. (Ma non se ce ne è già uno!) – kindall

+0

Non ci ho pensato! Ci proverò. Grazie! – cpb2

0

Apparentemente sono in ritardo nel rispondere a questa domanda, ma mi sono imbattuto per un po 'di tempo fa. Ho scritto qualcosa di simile alle tue esigenze me stesso.

È più un ripetitore HTTP, ma il primo è il proxy stesso. Non è ancora completamente completo e per ora non mi viene letto niente - ma quelli sono nella mia lista delle cose da fare.

Ho usato mitmproxy per raggiungere questo obiettivo. Potrebbe non essere il pezzo di codice più elegante là fuori e ho usato molti hack qua e là per ottenere la funzionalità del ripetitore. So che mitmproxy di default ha modi per raggiungere facilmente il ripetitore, ma nel mio caso c'era qualche requisito in cui non potevo usare quelle caratteristiche offerte da mitmproxy.

È possibile trovare il progetto a https://github.com/c0n71nu3/python_repeater/ Il repository è ancora in fase di aggiornamento da parte mia come e quando ci sono degli sviluppi.

Speriamo che possa essere di aiuto.