-
-
Save rgchris/73510e7d643eb0a6b9fa69b849cd9880 to your computer and use it in GitHub Desktop.
| Rebol [ | |
| Title: "HTTPD Scheme" | |
| Date: 10-Jun-2013 | |
| Author: [ | |
| "Christopher Ross-Gill" 4-Jan-2017 "Adaptation to Scheme" | |
| "Andreas Bolka" 4-Nov-2009 "A Tiny HTTP Server" | |
| ] | |
| File: %httpd.reb | |
| Version: 0.2.0 | |
| Rights: http://opensource.org/licenses/Apache-2.0 | |
| Purpose: { | |
| A Tiny Static Webserver Scheme for Rebol 3 | |
| Based on 'A Tiny HTTP Server' by Andreas Bolka | |
| https://github.com/earl/rebol3/blob/master/scripts/shttpd.r | |
| } | |
| ] | |
| attempt [_: none] ; for Rebolsource Rebol 3 Compatibility | |
| sys/make-scheme [ | |
| Title: "HTTP Server" | |
| Name: 'httpd | |
| Actor: [ | |
| Open: func [port [port!]][ | |
| ; probe port/spec | |
| port/locals: make object! [ | |
| subport: open [ | |
| scheme: 'tcp | |
| port-id: port/spec/port-id | |
| ] | |
| subport/awake: :port/scheme/awake-server | |
| subport/locals: make object! [ | |
| parent: :port | |
| body: _ | |
| ] | |
| ] | |
| port | |
| ] | |
| Close: func [port [port!]][ | |
| close port/locals/subport | |
| ] | |
| ] | |
| Status-Codes: make map! [ | |
| 200 "OK" 400 "Forbidden" 404 "Not Found" | |
| ] | |
| Respond: func [port response][ | |
| write port ajoin ["HTTP/1.0 " response/status " " status-codes/(response/status) crlf] | |
| write port ajoin ["Content-Type: " response/type crlf] | |
| write port ajoin ["Content-Length: " length? response/content crlf] | |
| write port crlf | |
| ;; Manual chunking is only necessary because of several bugs in R3's | |
| ;; networking stack (mainly cc#2098 & cc#2160; in some constellations also | |
| ;; cc#2103). Once those are fixed, we should directly use R3's internal | |
| ;; chunking instead: `write port body`. | |
| port/locals/body: to binary! response/content | |
| ] | |
| Send-Chunk: func [port [port!]][ | |
| ;; Trying to send data >32'000 bytes at once will trigger R3's internal | |
| ;; chunking (which is buggy, see above). So we cannot use chunks >32'000 | |
| ;; for our manual chunking. | |
| either empty? port/locals/body [_][ | |
| attempt [write port take/part port/locals/body 32'000] | |
| ] | |
| ] | |
| Awake-Client: use [from-actions chars][ | |
| from-actions: ["GET" | "POST"] | |
| chars: complement union space: charset " " charset [#"^@" - #"^_"] | |
| func [event [event!] /local port request response][ | |
| port: event/port | |
| switch event/type [ | |
| read [ | |
| either find port/data to-binary rejoin [crlf crlf][ | |
| response: port/locals/parent/awake request: make object! [ | |
| action: target: _ | |
| parse to-string port/data [ | |
| copy action from-actions some space | |
| copy target some chars some space | |
| "HTTP/" ["1.0" | "1.1"] | |
| ] | |
| ] | |
| respond port response | |
| ][ | |
| read port | |
| ] | |
| ] | |
| wrote [unless send-chunk port [close port] port] | |
| close [close port] | |
| ] | |
| ] | |
| ] | |
| Awake-Server: func [event [event!] /local client] [ | |
| if event/type = 'accept [ | |
| client: first event/port | |
| client/awake: :awake-client | |
| read client | |
| ] | |
| event | |
| ] | |
| ] |
| Rebol [ | |
| Title: "Test HTTPD Scheme" | |
| Date: 31-Jan-2017 | |
| Author: "Christopher Ross-Gill" | |
| File: %test-httpd.reb | |
| Version: 0.1.0 | |
| Rights: http://opensource.org/licenses/Apache-2.0 | |
| ] | |
| do %httpd.reb | |
| server: open [ | |
| Scheme: 'httpd | |
| Port-ID: 8080 | |
| Awake: func [ | |
| request [object!] | |
| ][ | |
| make object! compose [ | |
| Status: 200 | |
| Type: "text/html" | |
| Content: reword "<h1>OK! $action :: $target</h1>" compose [ | |
| action (request/action) | |
| target (request/target) | |
| ] | |
| ] | |
| ] | |
| ] | |
| attempt [browse http://127.0.0.1:8080/try/this/path] | |
| wait server |
It is big mistake to form the request header using multiple write calls like you do here: https://gist.github.com/rgchris/73510e7d643eb0a6b9fa69b849cd9880#file-httpd-reb-L53-L56
The thing is, that you should not write port until you receive wrote event from previous write action. Which is actually what you do with Send-Chunk. If you don't wait, you risc that the data are not sent yet and the second write interfere with the first one.
Another issue is, that in the Send-Chunk call is used take call, which internally move content of the output buffer, which is also not efficient. It also causes a crash when the data require internal loop (over 32000). That is something what should be examined. Although you are on Ren-C anyway and there will not be single line same today.
The mentioned bug 2098 was fixed with this patch: https://www.curecode.org/rebol3/ticket.rsp?id=2098
And I was not able to reproduce this: metaeducation/rebol-issues#2160 with this script not using multiple writes and sending a big file in one write.
I've modified the scheme a little bit here: https://gist.github.com/Oldes/ece2f714b73d305ccf517463a2760fe6
At this time, the REQUEST object is populated only with ACTION (HTTP's GET/POST/etc) and TARGET (filepath).