A server-side web framework written in Forth. http://www.1-9-9-1.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
5.4KB

  1. \ 1991
  2. include unix/socket.fs
  3. \ Helper words
  4. : +s ( addr1 u1 addr2 u2 -- addr3 u3 ) \ like s+ but prepend rather than append.
  5. 2swap s+ ;
  6. : exchange ( a1 a2 -- )
  7. 2dup c@ swap c@ rot c! swap c! ;
  8. : reverse ( caddr u -- ) \ reverse a string
  9. 1- bounds begin 2dup > while
  10. 2dup exchange
  11. -1 /string
  12. repeat 2drop ;
  13. \ User-defined routing
  14. wordlist constant routes
  15. : find-route ( addr u -- data )
  16. routes search-wordlist if
  17. >body @
  18. else 0 then ;
  19. : register-route ( data addr u -- )
  20. 2dup routes search-wordlist if
  21. routes drop nip nip
  22. >body !
  23. else
  24. routes get-current >r set-current \ switch definition word lists
  25. nextname create ,
  26. r> set-current
  27. then ;
  28. \ Public directory
  29. : pubvar create 0 , 0 , ;
  30. pubvar public
  31. : set-public-path ( addr u -- )
  32. public 2! ;
  33. : get-public-path ( -- addr u )
  34. public 2@ ;
  35. \ Query params
  36. pubvar queryString
  37. : set-query-string ( addr u -- )
  38. queryString 2! ;
  39. : get-query-string ( -- addr u )
  40. queryString 2@ ;
  41. \ Request's Content-Type
  42. pubvar RequestContentType
  43. : set-content-type ( addr u -- )
  44. RequestContentType 2! ;
  45. : get-content-type ( -- addr u )
  46. RequestContentType 2@ ;
  47. : filetype: ( addr u "extension" -- ) \ takes a content-type and the extension
  48. create here over 1+ allot place
  49. does> count ;
  50. : get-filetype ( addr u -- caddr cu ) \ takes an extension, looks to find a definition
  51. find-name dup if
  52. name>int
  53. execute
  54. else
  55. drop
  56. s" text/plain"
  57. then ;
  58. s" text/plain" filetype: txt \ txt should always be defined
  59. s" text/html" filetype: html
  60. s" text/css" filetype: css
  61. s" text/javascript" filetype: js
  62. s" image/png" filetype: png
  63. s" image/gif" filetype: gif
  64. s" image/jpeg" filetype: jpg
  65. s" image/jpeg" filetype: jpeg
  66. s" image/x-icon" filetype: ico
  67. \ Internal request handling
  68. : HTTP/1.1 s" HTTP/1.1 " ;
  69. : response-status ( u -- addr u )
  70. dup case \ get status code info
  71. 200 of s" OK" endof
  72. 404 of s" Not Found" endof
  73. endcase
  74. s\" \n" s+
  75. rot s>d <# #s #> +s \ convert status code to string and prepend to info
  76. HTTP/1.1 +s ; \ prepend HTTP/1.1
  77. : content-type ( addr u -- caddr cu )
  78. s" Content-Type: " +s \ Prepend to the provided content type
  79. s\" \n\n" s+ ; \ Append 2 new lines
  80. : set-header ( u addr u -- raddr ru ) \ Accepts status code and content type string
  81. rot response-status \ Set response status
  82. 2swap content-type \ Set content-type
  83. s+ ; \ Join
  84. : read-request ( socket -- addr u )
  85. pad 4096 read-socket ;
  86. : send-response ( addr u socket -- )
  87. dup >r write-socket r> close-socket ;
  88. : store-query-string ( addr u -- raddr ru )
  89. 2dup s" ?" search if
  90. 2dup set-query-string \ store query string
  91. swap drop -
  92. else
  93. s" " set-query-string \ store empty query string (reset)
  94. 2drop
  95. then ;
  96. : requested-route ( addr u -- raddr ru )
  97. bl scan 1- swap 1+ swap
  98. 2dup bl scan swap drop - \ get the space-separated route
  99. store-query-string ; \ strip and store the query, leave route
  100. : file-exists? ( addr u -- addr u bool )
  101. 2dup file-status nip 0= ;
  102. : .extension ( addr u -- addr u )
  103. 2dup reverse \ reverse the file name
  104. 2dup s" ." search \ search for the first occurance of "."
  105. if
  106. swap drop - \ remove the "." from the search results
  107. else
  108. s" txt"
  109. then
  110. 2dup reverse ; \ reverse reversed extension
  111. : serve-file-type ( addr u -- )
  112. .extension get-filetype set-content-type ;
  113. : serve-file ( addr u -- addr u )
  114. slurp-file ;
  115. : 404content-type txt ;
  116. : 404html s" 404";
  117. : either-resolve ( addr u -- resolveaddr resolveu )
  118. s" GET" search if
  119. s" html" get-filetype set-content-type \ reset the request's content-type
  120. requested-route
  121. 2dup find-route dup if
  122. >r 2drop r> \ keep xt, drop the route string
  123. execute \ execute the user's route handler
  124. else
  125. drop \ drop the null xt
  126. get-public-path +s \ see if route exists in public dir
  127. file-exists? if
  128. 2dup serve-file \ collect file contents
  129. 2swap serve-file-type \ set the file type
  130. else
  131. exit \ continue to 404
  132. then
  133. then
  134. 200 get-content-type set-header +s
  135. rdrop exit then ;
  136. : or-404 ( addr u -- 404addr 404u )
  137. 2drop
  138. 404 404content-type set-header 404html s+ ;
  139. : prepare-response ( addr u -- returnaddr returnu)
  140. either-resolve or-404 ;
  141. : start-server { server client }
  142. begin
  143. server 255 listen
  144. server accept-socket to client
  145. client read-request prepare-response client send-response
  146. again ;
  147. \ Userland
  148. : 1991: ( port -- )
  149. create-server 0 start-server ;
  150. : /1991 ( "<path> <word>" -- )
  151. bl word ' swap count register-route ;