3 # A Tiny HTTP Server: a stupid project to learn about HTTP.
5 # Status: can retrieve a few things :)
9 # - Better HTTP error handling
10 # - Which are mandatory? Which are not?
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
):
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 resp
+= resource
.content
80 def __init__(self
, resource
):
81 self
.__build
_response
(resource
)
84 def __get_method(self
, type_str
):
85 if type_str
== 'HEAD':
86 return HttpRequestMethod
.HEAD
87 elif type_str
== 'GET':
88 return HttpRequestMethod
.GET
89 elif type_str
== 'POST':
90 return HttpRequestMethod
.POST
91 elif type_str
== 'PUT':
92 return HttpRequestMethod
.PUT
93 elif type_str
== 'TRACE':
94 return HttpRequestMethod
.TRACE
95 elif type_str
== 'OPTIONS':
96 return HttpRequestMethod
.OPTIONS
97 elif type_str
== 'CONNECT':
98 return HttpRequestMethod
.CONNECT
99 elif type_str
== 'DELETE':
100 return HttpRequestMethod
.DELETE
102 def __get_version(self
, ver_str
):
103 if ver_str
== 'HTTP/1.0':
105 elif ver_str
== 'HTTP/1.1':
108 def __parse_request(self
, buf
):
110 (method
, resource
, version
) = range(0, 3)
114 met_str
= uri_str
= ver_str
= ''
125 if req_pos
== method
:
127 elif req_pos
== resource
:
129 elif req_pos
== version
:
134 headers_list
.append(header_cur
)
138 self
.headers
= headers_list
140 self
.method
= self
.__get
_method
(met_str
)
141 self
.version
= self
.__get
_version
(ver_str
)
144 s
= '%d %s %d\n' % (self
.method
, self
.uri
, self
.version
)
145 for header
in self
.headers
:
149 def __init__(self
, buf
):
150 self
.__parse
_request
(buf
)
153 def __serve_request(self
, request
, csock
):
154 assert request
.method
== HttpRequestMethod
.GET
155 resource
= HttpResource(self
.root
, request
.uri
)
156 resp
= HttpResponse(resource
)
157 csock
.sendall(resp
.response
)
158 print '-> served: ' + request
.uri
160 def __read_request(self
, csock
):
163 buf
+= csock
.recv(4096)
164 if buf
.endswith('\r\n\r\n'):
165 return HttpRequest(buf
)
167 def __create_sock(self
, domain
, port
):
168 s
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
169 s
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
, 1)
170 s
.bind((domain
, port
))
176 (csock
, addr
) = self
.sock
.accept()
177 print '-> Connection from: %s:%d' % (addr
[0],addr
[1])
178 r
= self
.__read
_request
(csock
)
180 self
.__serve
_request
(r
, csock
)
184 def __init__(self
, domain
, port
, root
):
186 self
.sock
= self
.__create
_sock
(domain
, port
)
190 print 'tiny-http <domain> <port> <root>'
193 server
= TinyHttp(argv
[1], int(argv
[2]), argv
[3])
196 if __name__
== '__main__':