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.

278 lines
9.9KB

  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. : sourcedir ( -- saddr su )
  14. \ Returns the directory in which the file
  15. \ invoking the word finds itself
  16. \ relative to gforth's execution directory.
  17. \ Useful for specifying in which dir to find
  18. \ specific files (e.g., public/, views/).
  19. sourcefilename \ get the name of our file
  20. pad dup >r place \ copy the string so we don't
  21. r> count \ modify sourcefilename.
  22. 2dup reverse \ reverse and search for first /
  23. s" /" search if \ if found, reverse string to
  24. 2dup reverse \ strip the filename but keep dir.
  25. else
  26. 2drop \ no slash,
  27. s" ./" \ same dir execution.
  28. then ;
  29. : file-exists? ( addr u -- addr u bool )
  30. 2dup file-status nip 0= ;
  31. \ User-defined routing
  32. wordlist constant routes
  33. : find-route ( addr u -- data )
  34. routes search-wordlist if
  35. >body @
  36. else 0 then ;
  37. : register-route ( data addr u -- )
  38. 2dup routes search-wordlist if
  39. routes drop nip nip
  40. >body !
  41. else
  42. routes get-current >r set-current \ switch definition word lists
  43. nextname create ,
  44. r> set-current
  45. then ;
  46. \ Public directory
  47. : pubvar create 0 , 0 , ;
  48. pubvar public
  49. : set-public-path ( addr u -- )
  50. public 2! ;
  51. : get-public-path ( -- addr u )
  52. public 2@ ;
  53. sourcedir s" public" s+ set-public-path
  54. \ Views directory
  55. pubvar views
  56. : set-view-path ( addr u -- )
  57. views 2! ;
  58. : get-view-path ( -- addr u )
  59. views 2@ ;
  60. sourcedir s" views/" s+ set-view-path \ Needs that trailing slash
  61. \ Handling views
  62. pubvar viewoutput
  63. : set-view-output ( addr u -- )
  64. viewoutput 2! ;
  65. : get-view-output ( -- addr u )
  66. viewoutput 2@ ;
  67. : parse-view ( addr u -- )
  68. \ Get string between <$ $> and invoke `evaluate`.
  69. \ Append to viewoutput as we go.
  70. \ There's probably a better way of doing this
  71. \ but it works for me.
  72. begin
  73. 2dup s" <$" search if
  74. 2over swap 2>r \ If there is a match for <$, save addr and u.
  75. swap >r dup >r \ Store the match and output anything that
  76. - \ comes before it.
  77. get-view-output +s
  78. set-view-output
  79. r> r> swap \ Then reinstate the match "<$...".
  80. 2dup s" $>" search if \ Check to see if there's a closing tag $>.
  81. 2 - \ Add the close tag to the search result.
  82. swap drop \ save the end position of $>.
  83. dup >r
  84. - \ Reduce the string to <$ ... $>.
  85. evaluate \ Run user's code (maybe a bad idea?).
  86. r> \ Retrieve our saved end position of $>.
  87. r> r> \ Retrive the addr u from start of loop iter.
  88. rot \ Bring end $> to stack top.
  89. over >r \ Store the real string's length.
  90. - \ Subtract end $> from u to get the pos from top
  91. r> swap \ that we'd like to strip away. Restore saved u.
  92. /string \ Drop top of the string until the end of $>.
  93. 0 \ Keep looping.
  94. else
  95. get-view-output +s \ No closing tag. Just save the full string.
  96. set-view-output
  97. 2rdrop \ And drop the stored addr and u
  98. 2drop \ as well as both the 2dup we made before
  99. 2drop \ searching twice.
  100. -1 \ Exit the loop.
  101. then
  102. else
  103. 2drop \ No match for <$. Drop the 2dup from before search.
  104. get-view-output +s
  105. set-view-output \ Save string as-is to view output
  106. -1 \ exit the loop
  107. then
  108. until ;
  109. : render-view ( addr u -- vaddr vu ) \ Accepts a view filename. Returns parsed contents.
  110. s" " set-view-output
  111. get-view-path +s
  112. file-exists? if
  113. slurp-file
  114. parse-view
  115. else
  116. exit \ Continue to 404, no view.
  117. then
  118. get-view-output ;
  119. : <$ ( -- ) ; \ Do nothing.
  120. : $> ( -- ) ; \ Do nothing.
  121. : $type ( addr u -- ) \ User-land word for outputing via views.
  122. get-view-output +s
  123. set-view-output ;
  124. : import ( -- ) \ User-land word for including other view files.
  125. get-view-path +s
  126. file-exists? if
  127. slurp-file
  128. parse-view
  129. else
  130. s" "
  131. then ;
  132. \ Query params
  133. pubvar queryString
  134. : set-query-string ( addr u -- )
  135. queryString 2! ;
  136. : get-query-string ( -- addr u )
  137. queryString 2@ ;
  138. \ Request's Content-Type
  139. pubvar RequestContentType
  140. : set-content-type ( addr u -- )
  141. RequestContentType 2! ;
  142. : get-content-type ( -- addr u )
  143. RequestContentType 2@ ;
  144. : filetype: ( addr u "extension" -- ) \ takes a content-type and the extension
  145. create here over 1+ allot place
  146. does> count ;
  147. : get-filetype ( addr u -- caddr cu ) \ takes an extension, looks to find a definition
  148. find-name dup if
  149. name>int
  150. execute
  151. else
  152. drop
  153. s" text/plain"
  154. then ;
  155. s" text/plain" filetype: txt \ txt should always be defined
  156. s" text/html" filetype: html
  157. s" text/css" filetype: css
  158. s" text/javascript" filetype: js
  159. s" image/png" filetype: png
  160. s" image/gif" filetype: gif
  161. s" image/jpeg" filetype: jpg
  162. s" image/jpeg" filetype: jpeg
  163. s" image/x-icon" filetype: ico
  164. \ Internal request handling
  165. : HTTP/1.1 s" HTTP/1.1 " ;
  166. : response-status ( u -- addr u )
  167. dup case \ get status code info
  168. 200 of s" OK" endof
  169. 404 of s" Not Found" endof
  170. endcase
  171. s\" \n" s+
  172. rot s>d <# #s #> +s \ convert status code to string and prepend to info
  173. HTTP/1.1 +s ; \ prepend HTTP/1.1
  174. : content-type ( addr u -- caddr cu )
  175. s" Content-Type: " +s \ Prepend to the provided content type
  176. s\" \n\n" s+ ; \ Append 2 new lines
  177. : set-header ( u addr u -- raddr ru ) \ Accepts status code and content type string
  178. rot response-status \ Set response status
  179. 2swap content-type \ Set content-type
  180. s+ ; \ Join
  181. : read-request ( socket -- addr u )
  182. pad 4096 read-socket ;
  183. : send-response ( addr u socket -- )
  184. dup >r write-socket r> close-socket ;
  185. : store-query-string ( addr u -- raddr ru )
  186. 2dup s" ?" search if
  187. 2dup set-query-string \ store query string
  188. swap drop -
  189. else
  190. s" " set-query-string \ store empty query string (reset)
  191. 2drop
  192. then ;
  193. : requested-route ( addr u -- raddr ru )
  194. bl scan 1- swap 1+ swap
  195. 2dup bl scan swap drop - \ get the space-separated route
  196. store-query-string ; \ strip and store the query, leave route
  197. : .extension ( addr u -- addr u )
  198. 2dup reverse \ reverse the file name
  199. 2dup s" ." search \ search for the first occurance of "."
  200. if
  201. swap drop - \ remove the "." from the search results
  202. else
  203. s" txt"
  204. then
  205. 2dup reverse ; \ reverse reversed extension
  206. : serve-file-type ( addr u -- )
  207. .extension get-filetype set-content-type ;
  208. : serve-file ( addr u -- addr u )
  209. slurp-file ;
  210. : 404content-type txt ;
  211. : 404html s" 404";
  212. : either-resolve ( addr u -- resolveaddr resolveu )
  213. s" GET" search if
  214. s" html" get-filetype set-content-type \ reset the request's content-type
  215. requested-route
  216. 2dup find-route dup if
  217. >r 2drop r> \ keep xt, drop the route string
  218. execute \ execute the user's route handler
  219. else
  220. drop \ drop the null xt
  221. get-public-path +s \ see if route exists in public dir
  222. file-exists? if
  223. 2dup serve-file \ collect file contents
  224. 2swap serve-file-type \ set the file type
  225. else
  226. exit \ continue to 404
  227. then
  228. then
  229. 200 get-content-type set-header +s
  230. rdrop exit then ;
  231. : or-404 ( addr u -- 404addr 404u )
  232. 2drop
  233. 404 404content-type set-header 404html s+ ;
  234. : prepare-response ( addr u -- returnaddr returnu)
  235. either-resolve or-404 ;
  236. : start-server { server client }
  237. begin
  238. server 255 listen
  239. server accept-socket to client
  240. client read-request prepare-response client send-response
  241. again ;
  242. \ Userland
  243. : 1991: ( port -- )
  244. create-server 0 start-server ;
  245. : /1991 ( "<path> <word>" -- )
  246. bl word ' swap count register-route ;