1 # Copyright (C) 2006-2008, Parrot Foundation.
6 examples/io/httpd.pir - HTTP server
10 $ ./parrot examples/io/httpd.pir
14 A very tiny HTTP-Server. It currently only understands the GET method.
15 It's a nice way of testing pretty much all IO functions.
16 By default (and not yet configurable) it binds to localhost:1234.
18 =head2 Serving Parrot Docs
20 If no filename is given it serves the HTML documentation
21 in ./docs/html. Make sure you have built them with
25 After that you can browse the documentation with
31 http://localhost:1234/docs/html/index.html
33 =head2 Serving Other HTML Files
35 If a html file is present in the request, this file will be served:
37 http://localhost:1234/index.html
39 This will sent F<./index.html> from the directory, where F<httpd.pir>
44 If the file extension is C<.pir> or C<.pbc>, this file will be loaded
45 below the directory F<cgi-pir> and the function C<cgi_main> will be
46 invoked with the query as an argument.
47 This functions should return a plain string, which will be sent to the
50 F<cgi_main> is called with 3 arguments: a todo/reserved PMC, a string
51 with the original query and a Hash, with C<key=value> items split by
52 C<'+'>. C<key> and C<value> are already C<urldecoded>.
56 .param pmc reserved # TODO
57 .param string query # all after '?': "foo=1+bar=A"
58 .param pmc query_hash # Hash { foo=>'1', bar=>'A' }
59 .return ("<p>foo</p>") # in practice use a full <html>doc</html>
60 # unless serving XMLHttpRequest's
65 http://localhost:1234/foo.pir?foo=1+bar=%61
67 will serve, whatever the C<cgi_main> function returned.
71 make it work on W32/IE
73 Transcode the received string to ascii, in order to have access to an
74 implemented 'index' op. Or just use unicode instead.
82 Original author is Markus Amsler - <markus.amsler@oribi.org>
83 The code was heavily hacked by bernhard and leo.
87 .const string CRLF = "\r\n"
88 .const string CRLFCRLF = "\r\n\r\n"
89 .const string LFLF = "\n\n"
90 .const string CRCR = "\r\r"
92 .const string SERVER_NAME = "Parrot-httpd/0.1"
95 .include 'except_types.pasm'
96 .include 'socket.pasm'
102 .local pmc listener, work, fp
103 .local pmc fp # read requested files from disk
107 .local string buf, req, rep, temp
108 .local string meth, url, file_content
110 .local int len, pos, occ1, occ2, dotdot
112 .local string doc_root
117 # TODO provide sys/socket constants
118 listener = new 'Socket'
119 listener.'socket'(.PIO_PF_INET, .PIO_SOCK_STREAM, .PIO_PROTO_TCP) # PF_INET, SOCK_STREAM, tcp
120 unless listener goto ERR_NO_SOCKET
122 # Pack a sockaddr_in structure with IP and port
123 address = listener.'sockaddr'(host, port)
124 ret = listener.'bind'(address)
125 if ret == -1 goto ERR_bind
127 print "Running webserver on port "
132 print "The Parrot documentation can now be accessed at http://"
137 print "Be sure that the HTML docs have been generated with 'make html'.\n"
141 work = listener.'accept'()
146 # encodingname S1, I0
149 # print "\nencoding of buf: "
153 # print "\nafter buf"
156 if ret <= 0 goto SERVE_REQ
158 index pos, req, CRLFCRLF
161 if pos >= 0 goto SERVE_REQ
165 if pos >= 0 goto SERVE_REQ
169 if pos >= 0 goto SERVE_REQ
176 .local string response
178 response = '500 Internal Server Error'
180 headers['Server'] = SERVER_NAME
185 substr meth, req, 0, occ1
187 index occ2, req, " ", occ1
189 substr url, req, occ1, len
191 if meth == "GET" goto SERVE_GET
193 print "unknown method:'"
201 (is_cgi, file_content, len) = check_cgi(url)
202 if is_cgi goto SERVE_blob
207 # Security: Don't allow access to the parent dir
208 index dotdot, url, ".."
209 if dotdot >= 0 goto SERVE_404
211 # redirect instead of serving index.html
212 if url == "/" goto SERVE_docroot
214 # Those little pics in the URL field or in tabs
215 if url == "/favicon.ico" goto SERVE_favicon
217 # try to serve a file
221 # try to open the file in url
222 concat url, doc_root, url
224 eh = new 'ExceptionHandler'
225 set_addr eh, handle_404_exception
226 eh.'handle_types'(.EXCEPTION_PIO_ERROR)
230 unless fp goto SERVE_404
231 len = stat url, .STAT_FILESIZE
232 read file_content, fp, len
236 send_response(work, response, headers, file_content)
237 # TODO provide a log method
238 print "served file '"
244 response = '301 Moved Permanently'
245 headers['Location'] = '/docs/html/index.html'
246 file_content = "Please go to <a href='docs/html/index.html'>Parrot Documentation</a>."
247 send_response(work, response, headers, file_content)
248 print "Redirect to 'docs/html/index.html'\n"
252 url = urldecode( '/docs/resources/favicon.ico')
255 handle_404_exception:
259 say "Trapped file not found exception."
263 response = '404 Not found'
264 file_content = response
265 send_response(work, response, headers, file_content)
266 print "File not found: '"
272 print "Could not open socket.\n"
275 print "bind failed\n"
282 # send_response(socket, response_code, headers, body)
283 # sends HTTP response to the socket and closes the socket afterwards.
289 .local string rep, temp, headername
291 .local pmc headers_iter
295 rep .= "Connection: close"
297 ret = exists headers['Content-Length']
298 if ret goto SKIP_CONTENT_LENGTH
300 temp = to_string (len)
301 headers['Content-Length'] = temp
304 headers_iter = iter headers
306 headername = shift headers_iter
309 temp = headers[headername]
312 if headers_iter goto HEADER_LOOP
316 ret = sock.'send'(rep)
322 .param pmc args :slurpy
325 ret = sprintf "%d", args
329 # convert %xx to char
333 .local string out, char_in, char_out
334 .local int c_out, pos_in, len
341 if pos_in >= len goto END
342 substr char_in, in, pos_in, 1
344 if char_in != "%" goto INC_IN
345 # OK this was a escape character, next two are hexadecimal
347 substr hex, in, pos_in, 2
348 c_out = hex_to_int (hex)
362 .tailcall hex.'to_int'(16)
365 # if file is *.pir or *.pbc run it as CGI
368 $I0 = index url, ".pir"
369 if $I0 > 0 goto cgi_1
370 $I0 = index url, ".pbc"
371 if $I0 > 0 goto cgi_1
374 # file.pir?foo=1+bar=2
376 if $I0 == -1 goto no_query
377 .local string file, query
378 .local pmc query_hash
379 file = substr url, 0, $I0
381 query = substr url, $I0
382 # TODO split into a hash, then decode parts
383 query_hash = make_query_hash(query)
384 query = urldecode(query)
389 query_hash = new 'Hash'
392 file = urldecode(file)
394 # Security: Don't allow access to the parent dir
396 index dotdot, file, ".."
397 if dotdot < 0 goto cgi_file
406 file = "cgi-pir/" . file
412 result = 'cgi_main'($P0, query, query_hash)
414 .return (1, result, $I0)
417 # split query at '+', make hash from foo=bar items
419 .param string query # the unescapced one
420 .local pmc query_hash, items
421 .local string kv, k, v
422 query_hash = new 'Hash'
423 items = split '+', query
430 if $I0 == -1 goto no_val
431 k = substr kv, 0, $I0
445 if i < n goto lp_items
453 # vim: expandtab shiftwidth=4 ft=pir: