ren
ren

Reputation: 115

How can I use a Python streaming socket as a proxy?

I'm trying to write a python process which listens on a port and when a client connects to it, it starts up a thread which does the following:

  1. Connects to a remote service (http://193.108.24.18:8000/magicFM)

  2. Passes any data received to the connected client (which happens to be Windows Media Player)

The story is that I want to listen to my radio at work, but I cannot because I am in another country(available only nationally) and I cannot change the proxy settings on my computer.... But I have this server which I would like to use as a proxy.

Thanks in advance.

Here's what I did so far:

#!/usr/bin/env python
import socket, urllib2

TCP_IP = '0.0.0.0'
TCP_PORT = 5566
BUFFER_SIZE = 16 * 1024  #16 kb/s
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connection address:', addr
req = urllib2.urlopen('http://193.108.24.18:8000/magicFM')

while 1:
    chunk = req.read(BUFFER_SIZE)
    if not chunk: break
    conn.send(chunk)


conn.close()

but it fails... with:

Traceback (most recent call last):
  File "./magicfmproxy.py", line 17, in ?
    conn.send(chunk)
socket.error: (32, 'Broken pipe')

Upvotes: 1

Views: 3697

Answers (3)

Andrey Tatarinov
Andrey Tatarinov

Reputation: 570

To extend glglgl answer: your problem is in breaking protocol.

HTTP protocol specifies:

  • user request GET /magicFM ...
  • server response with metadata 200 OK ...
  • server continued response with actual data

See more for details: http://en.wikipedia.org/wiki/HTTP

urllib2.urlopen hides all this complication from you making it look as simple as reading file, though your client expect proxy to behave as normal http-server. Here urlopen is a wrong abstraction for you. Best strategy would be to open socket to server and start two parallel loops:

  • reading from client, writing to server
  • reading from server, writing to client

(or do it in one loop with non-blocking reads; or do asyncio)

There might be a complication: http-protocol specifies "Host" header which your client will send in request to proxy with proxy address as a value, depending on behavior of your radio-server you might need to rewrite "Host: ..." in client request to correct address (though in modern Internet usually it doesn't matter).

Also interesting side effect you will notice would be: proxy would not contain any information about specific URLs to open, as your media client will provide them for you.

Upvotes: 0

glglgl
glglgl

Reputation: 91099

I can only guess, but maybe your problem lives on the client side.

I don't know which connections your client tries to establish, but maybe there is a clash between what is expected and what is really transmitted:

  • You get a connection from your client, which probably sends some request data.
  • If these don't match with the data you send to the stream with urllib2.urlopen(), or the answer from there doesn't match, the client cancels the connection, letting you have a broken socket.

I see two solutions:

Either

  • Try to send the answer line (HTTP/x.x 200 OK or such) and the headers back to your client as well - it should be somewhere in req.headers or so.

Or

  • don't do a urllib2.urlopen() at all, but just open a regular socket connection to there. But then you'll probably have to tamper with the headers of the request - the Host: header will probably have to be replaced.

Upvotes: 1

hochl
hochl

Reputation: 12950

As a start, to connect to a remote site using TCP, use this code

import socket, struct

def connectToHost(host, port=80, timeout=0):
    try:
            sock=socket.socket()
            timeval=struct.pack("2I", timeout, 0)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeval)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDTIMEO, timeval)
            sock.connect((host, port))
            return sock
    except:
            return None

You now have an open socket connected to a remote server. You would have to create a listen socket and wait on this one for a connection. As soon as the connection is there, multiplex data streams using select.

I don't have the time right now, this code is more of a sketch how it might look like. You would need proper error handling and maybe nice error messages in this function, but if noone comes up with a complete solution I may make the effort to complete this code.

Upvotes: 2

Related Questions