26

I have a very simple web sever written in Python. It listens on port 13000, how can I make it deliver a simple "Hello World" webpage if http://localhost:13000 is opened in browser?

Right there is my code:

# set up socket and connection
while True:
    sock, addr = servSock.accept()
    # WHAT GOES HERE?
    sock.close()

As you can see, I am not exactly sure how to actually send back the webpage?

I only have to use the socket library.

EDIT: The problem is not that I don't know how to formulate the HTTP response, I don't know how to actually make it display in my browser! It just keeps spinning/loading.

7 Answers 7

32

Updated according to question change

Possibly, it keeps spinning because in combination of absence of Content-Length and Connection headers, browser may assume it's Connection: keep-alive, so it continues to receive data from your server forever. Try to send Connection: close, and pass actual Content-Length to see if that helps.


Won't this do what you expect it to? :)
#!/usr/bin/env python
# coding: utf8

import socket

MAX_PACKET = 32768

def recv_all(sock):
    r'''Receive everything from `sock`, until timeout occurs, meaning sender
    is exhausted, return result as string.'''
    
    # dirty hack to simplify this stuff - you should really use zero timeout,
    # deal with async socket and implement finite automata to handle incoming data

    prev_timeout = sock.gettimeout()
    try:
        sock.settimeout(0.01)
    
        rdata = []
        while True:
            try:
                rdata.append(sock.recv(MAX_PACKET))
            except socket.timeout:
                return ''.join(rdata)
        
        # unreachable
    finally:
        sock.settimeout(prev_timeout)
    
def normalize_line_endings(s):
    r'''Convert string containing various line endings like \n, \r or \r\n,
    to uniform \n.'''
    
    return ''.join((line + '\n') for line in s.splitlines())

def run():
    r'''Main loop'''
    
    # Create TCP socket listening on 10000 port for all connections, 
    # with connection queue of length 1
    server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, \
                                socket.IPPROTO_TCP)
    server_sock.bind(('0.0.0.0', 13000))
    server_sock.listen(1)

    while True:
        # accept connection
        client_sock, client_addr = server_sock.accept()
        
        # headers and body are divided with \n\n (or \r\n\r\n - that's why we
        # normalize endings). In real application usage, you should handle 
        # all variations of line endings not to screw request body
        request = normalize_line_endings(recv_all(client_sock)) # hack again
        request_head, request_body = request.split('\n\n', 1)
        
        # first line is request headline, and others are headers
        request_head = request_head.splitlines()
        request_headline = request_head[0]
        # headers have their name up to first ': '. In real world uses, they
        # could duplicate, and dict drops duplicates by default, so
        # be aware of this.
        request_headers = dict(x.split(': ', 1) for x in request_head[1:])
        
        # headline has form of "POST /can/i/haz/requests HTTP/1.0"
        request_method, request_uri, request_proto = request_headline.split(' ', 3)
        
        response_body = [
            '<html><body><h1>Hello, world!</h1>',
            '<p>This page is in location %(request_uri)r, was requested ' % locals(),
            'using %(request_method)r, and with %(request_proto)r.</p>' % locals(),
            '<p>Request body is %(request_body)r</p>' % locals(),
            '<p>Actual set of headers received:</p>',
            '<ul>',
        ]
        
        for request_header_name, request_header_value in request_headers.iteritems():
            response_body.append('<li><b>%r</b> == %r</li>' % (request_header_name, \
                                                    request_header_value))
    
        response_body.append('</ul></body></html>')
    
        response_body_raw = ''.join(response_body)
        
        # Clearly state that connection will be closed after this response,
        # and specify length of response body
        response_headers = {
            'Content-Type': 'text/html; encoding=utf8',
            'Content-Length': len(response_body_raw),
            'Connection': 'close',
        }
    
        response_headers_raw = ''.join('%s: %s\n' % (k, v) for k, v in \
                                                response_headers.iteritems())
                
        # Reply as HTTP/1.1 server, saying "HTTP OK" (code 200).
        response_proto = 'HTTP/1.1'
        response_status = '200'
        response_status_text = 'OK' # this can be random
        
        # sending all this stuff
        client_sock.send('%s %s %s' % (response_proto, response_status, \
                                                        response_status_text))
        client_sock.send(response_headers_raw)
        client_sock.send('\n') # to separate headers from body
        client_sock.send(response_body_raw)
        
        # and closing connection, as we stated before
        client_sock.close()

run()

For more detailed description, please see description of HTTP protocol.

Sign up to request clarification or add additional context in comments.

9 Comments

Yes but...the browser just keeps "spinning" and nothing displays?
I've updated code sample to work 100% guaranteed :) I hope you will find basic principles of header handling useful, but I recommend you not to rely upon this kind of code and implement full featured HTTP request parser.
well, you've made a pretty complete answer... though I think the reason it was still spinning (until timeout) is that it was waiting for a double "\n". But at least, your code example is a good snippet to have under the hook, just in case ;)
Thanks. I think a \n has been omitted before response_headers_raw.
I know this is pretty late but I've copied your code and it doesn't work for me. I think it might be the request that is the problem. So is fetch("IP:PORT"). The IP is the public ip of the server?
|
11
# set up socket and connection
while True:
    sock, addr = servSock.accept()
    sock.send("""HTTP/1.1 200 OK
Content-Type: text/html

<html><body>Hello World</body></html>
""");
    sock.close()

Comments

6

Send back something like:

HTTP/1.1 200 OK
Date: Wed, 11 Apr 2012 21:29:04 GMT
Server: Python/6.6.6 (custom)
Content-Type: text/html

Then the actual html code. Make sure there is a newline after the Content-Type line and before the html.

Comments

3

or, if you just don't want to remember the full protocol, you can find it again using :

 % nc stackoverflow.com 80
GET / HTTP/1.1
Host: stackoverflow.com

HTTP/1.1 200 OK
Cache-Control: public, max-age=60
Content-Type: text/html; charset=utf-8
Expires: Wed, 11 Apr 2012 21:33:49 GMT
Last-Modified: Wed, 11 Apr 2012 21:32:49 GMT
Vary: *
Date: Wed, 11 Apr 2012 21:32:49 GMT
Content-Length: 206008

[...]
 % 

well, you shall usually prefer a site that is less verbose (usually serving only a static file) than stackoverflow ;)

The minimal requirements (you'll find on the answer) is :

sock.send(r'''HTTP/1.0 200 OK
Content-Type: text/plain

Hello, world!

''')

two returns are mandatory for the server to get the answer, otherwise the browser waits indefinitely for headers

But to mimic the behaviour of a webserver, don't forget to send your answer only after the browser sends you some data followed by two carriage returns, usually you can get what it sends using :

 % nc -kl localhost 13000
GET / HTTP/1.1
Host: localhost:13000
User-Agent: Mozilla/5.0...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive

 %

so you can improve your test routines

Comments

3

I took a previous answer and edited the code for Python3 utf-8 and bytes encoding. Thanks for the original answer it helped out a lot.

import socket

MAX_PACKET = 32768

def recv_all(sock):
    r'''Receive everything from `sock`, until timeout occurs, meaning sender
    is exhausted, return result as string.'''

    # dirty hack to simplify this stuff - you should really use zero timeout,
    # deal with async socket and implement finite automata to handle incoming data

    prev_timeout = sock.gettimeout()
    try:
        sock.settimeout(0.1)

        rdata = []
        while True:
            try:
                # Gotta watch for the bytes and utf-8 encoding in Py3
                rdata.append(sock.recv(MAX_PACKET).decode('utf-8')) 
            except socket.timeout:
                return ''.join(rdata)

        # unreachable
    finally:
        sock.settimeout(prev_timeout)

def normalize_line_endings(s):
    r'''Convert string containing various line endings like \n, \r or \r\n,
    to uniform \n.'''
    test = s.splitlines()
    return ''.join((line + '\n') for line in s.splitlines())

def run():
    r'''Main loop'''

    # Create TCP socket listening on 10000 port for all connections,
    # with connection queue of length 1
    server_sock = socket.socket(socket.AF_INET,
                                socket.SOCK_STREAM,
                                socket.IPPROTO_TCP)
    #Added the port 13001 for debuging purposes 

    try:
        server_sock.bind(('0.0.0.0', 13000))
        print('PORT 13000')
    except:
        server_sock.bind(('0.0.0.0', 13001))
        print('PORT 13001')
    # except:
    #     server_sock.bind(('0.0.0.0', 13002))
    #     print('PORT 13002')

    server_sock.listen(1)

    while True:
        # accept connection
        try:
            client_sock, client_addr = server_sock.accept()

            # headers and body are divided with \n\n (or \r\n\r\n - that's why we
            # normalize endings). In real application usage, you should handle
            # all variations of line endings not to screw request body
            request = normalize_line_endings(recv_all(client_sock)) # hack again

            request_head, request_body = request.split('\n\n', 1)

            # first line is request headline, and others are headers
            request_head = request_head.splitlines()
            request_headline = request_head[0]
            # headers have their name up to first ': '. In real world uses, they
            # could duplicate, and dict drops duplicates by default, so
            # be aware of this.
            request_headers = dict(x.split(': ', 1) for x in request_head[1:])

            # headline has form of "POST /can/i/haz/requests HTTP/1.0"
            request_method, request_uri, request_proto = request_headline.split(' ', 3)

            response_body = [
                '<html><body><h1 style="color:red">Hello, world!</h1>',
                '<p>This page is in location %(request_uri)r, was requested ' % locals(),
                'using %(request_method)r, and with %(request_proto)r.</p>' % locals(),
                '<p>Request body is %(request_body)r</p>' % locals(),
                '<p>Actual set of headers received:</p>',
                '<ul>',
            ]

            for request_header_name, request_header_value in request_headers.items():
                response_body.append('<li><b>%r</b> == %r</li>' % (request_header_name,
                                                                    request_header_value))

            response_body.append('</ul></body></html>')

            response_body_raw = ''.join(response_body)

            # Clearly state that connection will be closed after this response,
            # and specify length of response body
            response_headers = {
                'Content-Type': 'text/html; encoding=utf8',
                'Content-Length': len(response_body_raw),
                'Connection': 'close',
            }

            response_headers_raw = ''.join('%s: %s\n' % (k, v) for k, v in \
                                                    response_headers.items())

            # Reply as HTTP/1.1 server, saying "HTTP OK" (code 200).
            response_proto = 'HTTP/1.1'.encode()
            response_status = '200'.encode()
            response_status_text = 'OK'.encode() # this can be random

            # sending all this stuff
            client_sock.send(b'%s %s %s' % (response_proto, response_status,
                                                            response_status_text))
            client_sock.send(response_headers_raw.encode())
            client_sock.send(b'\n') # to separate headers from body
            client_sock.send(response_body_raw.encode())

            # and closing connection, as we stated before

        finally:
            client_sock.close()

run()

Comments

1

You might want to Checkout web objects http://www.webob.org/

It's a simple lightweight project for creating http compatible requests and responses. You can do just about anything with you requests/response objects ... Or just delegate the heavy lifting to WebObjects

Sample

>>> from webob import Response
>>> res = Response()
>>> res.status
'200 OK'
>>> res.headerlist
[('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]
>>> res.body
''

1 Comment

This doesn't show how to use it with sockets.
-1

Update to one of the solutions, because latest version asks to send data in byte format

while True:
    sock, addr = servSock.accept()
    sock.sendall(b"HTTP/1.1 200 OK\n"
         +b"Content-Type: text/html\n"
         +b"\n" # Important!
         +b"<html><body>Hello World</body></html>\n");
    sock.shutdown(soket.SHUT_WR)
    sock.close()

I could have edited above post, but the queue is full :(.
Also one can use encode() method to convert to byte fromat.

1 Comment

HTTP requires carriage return and a new line character, not just a new line.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.