2012-11-10 32 views
2

我正在編寫一個將使用Python的HTTPServer和BaseHTTPRequest的應用程序。在某些時候,我認爲由於數據用戶可能想要發送的敏感性質,SOCKS的實現將會很有用。問題在於 - 應用程序計劃在非標準端口上運行,因此如果可以與明文和SSL連接進行通信,它將非常有用。我發現there一種方法,使HttpServer的使用SSL:Python Magic(智能雙模式)SSL Socket?

import BaseHTTPServer, SimpleHTTPServer 
import ssl 

httpd = BaseHTTPServer.HTTPServer(('localhost', 4443), SimpleHTTPServer.SimpleHTTPRequestHandler) 
httpd.socket = ssl.wrap_socket (httpd.socket, certfile='path/to/localhost.pem', server_side=True) 
httpd.serve_forever() 

有沒有一種方法創建一個套接字類,將處理SSL和純文本連接?檢測SSL的簡單方法(即一些魔術字節)?另一種方法是分配兩個端口,但這不太酷。

回答

4

我已經調查了一下這個問題。 使插座表現得像兩臺不同的服務器一樣容易(取決於所接收數據的類型)。這裏最糟糕的是python的_ssl庫直接從socket._socket中讀取,它是一個本地python對象,因此無法正常掛接。

一種方法是編寫一個C模塊,它將鉤住原生Python套接字。 另一個解決方案是擁有1個前端和2個後端(https和http)。 Frontend監聽4443並決定是否應該使用https後端或http後端進行連接。您可以將相同的處理程序添加到兩臺服務器,並且它們的行爲方式相同。另一個問題是,在後端我們不知道客戶端的IP地址,但有一些解決方法(像前端將要保留的後端將要查看的dict {(前端到後端源端口號):客戶端IP} 。

與C解決方案相比,第二個看起來很骯髒,但在這裏。

import BaseHTTPServer, SimpleHTTPServer 
import ssl 
import socket 
import select 
import threading 

FRONTEND_PORT = 4443 
BACKEND_PORT_SSL = 44431 
BACKEND_PORT_HTTP = 44432 
HOST = 'localhost' 

httpd_ssl = BaseHTTPServer.HTTPServer((HOST, BACKEND_PORT_SSL), SimpleHTTPServer.SimpleHTTPRequestHandler) 
httpd_ssl.socket = ssl.wrap_socket (httpd_ssl.socket, certfile='key.pem', server_side=True) 

httpd_direct = BaseHTTPServer.HTTPServer((HOST, BACKEND_PORT_HTTP), SimpleHTTPServer.SimpleHTTPRequestHandler) 

def serve_forever(http_server): 
    http_server.serve_forever() 

def categorize(sock, addr): 
    data = sock.recv(1) 
    if data == '\x16': 
     port = BACKEND_PORT_SSL 
    else: 
     port = BACKEND_PORT_HTTP 
    other_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    other_sock.connect((HOST, port)) 
    other_sock.send(data) 
    inp = [sock, other_sock] 
    select_timeout = 1.0 
    try: 
     while 1: 
      r,w,x = select.select(inp,[],[],select_timeout) 
      if not r: 
       continue 
      for s in r: 
       o_s = inp[1] if inp[0]==s else inp[0] 
       buf = s.recv(4096) 
       if not buf: 
        raise socket.error 
       o_s.send(buf) 
    except socket.error: 
     pass 
    finally: 
     for s in inp: 
      s.close() 

threading.Thread(target=serve_forever, args=(httpd_ssl,)).start() 
threading.Thread(target=serve_forever, args=(httpd_direct,)).start() 

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
sock.bind((HOST, FRONTEND_PORT)) 
sock.listen(10) 

while True: 
    conn, addr = sock.accept() 
    threading.Thread(target=categorize, args=(conn, addr)).start() 
+0

哈克!我喜歡它。 – d33tah

+1

關於第一個想法。可以鉤住sock_recv(PySocketSockObject * s,PyObject * args)。但是這會影響所有套接字。所以我們必須找到區分鉤狀和非鉤狀插座的好方法。例如使用全局字典,但每個recv的字典查找將花費相當多,我不知道我們是否可以在PySocketSockObject結構中至少存儲一點。所以第一個解決方案更加骯髒。 –

+0

嗯,爲什麼不可能創建一個包含_sock對象的類,該對象的行爲就像真正的_sock和被包裝的對象之間的代理一樣? – d33tah

0

是的,there acutally is

但我不知道任何實現STARTLS HTTP的客戶端或服務器。它通常用於IMAP和SMTP,但對於HTTP而言,不幸的是似乎沒有任何實現,但通常在不同的端口上使用HTTP,然後使用HTTPS。

+0

我需要一個服務器端解決方案。 – d33tah

+0

沒有任何我知道的。順便說一句,你可能並不是真的指[SOCKS](http://en.wikipedia.org/wiki/SOCKS),而是SSL。 – mata

2

我解決了與在accept()偷窺到接收到的數據,然後返回一個輔助類的問題,無論是包裝還是赤裸裸的插座:

class SmartServerSocket: 
    def __init__(self, sock): 
     self.sock = sock 
     # delegate methods as needed 
     _delegate_methods = [ "fileno" ] 
     for method in _delegate_methods: 
      setattr(self, method, getattr(sock, method)) 
    def accept(self): 
     (conn, addr) = self.sock.accept() 
     if conn.recv(1, socket.MSG_PEEK) == "\x16": 
      return (ssl.wrap_socket(conn, certfile='path/to/localhost.pem', server_side=True), addr) 
     else: 
      return (conn, addr) 

httpd = BaseHTTPServer.HTTPServer(('localhost', 4443), SimpleHTTPServer.SimpleHTTPRequestHandler) 
httpd.socket = SmartServerSocket(httpd.socket) 
httpd.serve_forever() 

如果你喜歡,你可以給服務器對象設置爲SmartServerSocket的構造函數,並且accept()在那裏爲檢測到的協議設置了一個特殊的成員變量,因此您可以在RequestHandler實例中檢查它。