3 # A Tiny HTTP Server: a stupid project to learn about HTTP.
5 # Status: can retrieve a few things :)
9 # - Improve error handling
10 # - Generate good HTTP errors
11 # - Interpret client's headers
13 # Luiz Capitulino <lcapitulino@gmail.com>
15 from sys
import argv
,exit
16 import socket
, os
, time
, mimetypes
18 class HttpRequestMethod
:
29 def __get_length(self
):
30 return os
.path
.getsize(self
.path
)
32 def __get_content(self
):
38 def __get_content_type(self
):
39 ctype
= mimetypes
.guess_type(self
.path
)[0]
41 ctype
= 'application/octet-stream'
44 def __build_resource(self
, path
):
47 self
.length
= self
.__get
_length
()
49 # XXX: all errors? really?
53 self
.content
= self
.__get
_content
()
54 self
.content_type
= self
.__get
_content
_type
()
56 def __init__(self
, root
, abs_path
):
57 path
= root
+ '/' + abs_path
58 self
.__build
_resource
(path
)
61 def __build_response(self
, resource
, method
):
63 time_str
= time
.strftime('%a, %d %b %Y %H:%M:%S GMT', time
.gmtime())
65 # FIXME: There are number of possible errors, but we generate
67 resp
+= 'HTTP/1.1 404 Not Found\r\n'
69 resp
+= 'HTTP/1.1 200 OK\r\n'
70 resp
+= 'Date: %s\r\n' % time_str
71 resp
+= 'Server: TinyHttp/0.1\r\n'
72 resp
+= 'Connection: close\r\n'
73 if not resource
.error
:
74 resp
+= 'Content-Length: %d\r\n' % resource
.length
75 resp
+= 'Content-Type: %s\r\n' % resource
.content_type
77 if method
== HttpRequestMethod
.GET
:
78 resp
+= resource
.content
81 def __init__(self
, resource
, method
):
82 self
.__build
_response
(resource
, method
)
85 def __get_method(self
, type_str
):
86 if type_str
== 'HEAD':
87 return HttpRequestMethod
.HEAD
88 elif type_str
== 'GET':
89 return HttpRequestMethod
.GET
90 elif type_str
== 'POST':
91 return HttpRequestMethod
.POST
92 elif type_str
== 'PUT':
93 return HttpRequestMethod
.PUT
94 elif type_str
== 'TRACE':
95 return HttpRequestMethod
.TRACE
96 elif type_str
== 'OPTIONS':
97 return HttpRequestMethod
.OPTIONS
98 elif type_str
== 'CONNECT':
99 return HttpRequestMethod
.CONNECT
100 elif type_str
== 'DELETE':
101 return HttpRequestMethod
.DELETE
103 def __get_version(self
, ver_str
):
104 if ver_str
== 'HTTP/1.0':
106 elif ver_str
== 'HTTP/1.1':
109 def __parse_request(self
, buf
):
111 (method
, resource
, version
) = range(0, 3)
115 met_str
= uri_str
= ver_str
= ''
126 if req_pos
== method
:
128 elif req_pos
== resource
:
130 elif req_pos
== version
:
135 headers_list
.append(header_cur
)
139 self
.headers
= headers_list
141 self
.method
= self
.__get
_method
(met_str
)
142 self
.version
= self
.__get
_version
(ver_str
)
145 s
= '%d %s %d\n' % (self
.method
, self
.uri
, self
.version
)
146 for header
in self
.headers
:
150 def __init__(self
, buf
):
151 self
.__parse
_request
(buf
)
154 def __serve_request(self
, request
, csock
):
155 assert request
.method
== HttpRequestMethod
.GET
or \
156 request
.method
== HttpRequestMethod
.HEAD
157 resource
= HttpResource(self
.root
, request
.uri
)
158 resp
= HttpResponse(resource
, request
.method
)
159 csock
.sendall(resp
.response
)
160 print '-> served: ' + request
.uri
162 def __read_request(self
, csock
):
165 buf
+= csock
.recv(4096)
166 # XXX: Too lazy, supports only GET and HEAD
167 if buf
.endswith('\r\n\r\n'):
168 return HttpRequest(buf
)
170 def __create_sock(self
, domain
, port
):
171 s
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
172 s
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
, 1)
173 s
.bind((domain
, port
))
179 (csock
, addr
) = self
.sock
.accept()
180 print '-> Connection from: %s:%d' % (addr
[0],addr
[1])
181 r
= self
.__read
_request
(csock
)
183 self
.__serve
_request
(r
, csock
)
187 def __init__(self
, domain
, port
, root
):
189 self
.sock
= self
.__create
_sock
(domain
, port
)
193 print 'tiny-http <domain> <port> <root>'
196 server
= TinyHttp(argv
[1], int(argv
[2]), argv
[3])
199 if __name__
== '__main__':