A server-side web framework written in Forth. http://www.1-9-9-1.com
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

364 líneas
13KB

  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. : str-count ( addr1 u1 addr2 u2 -- u3 ) \ Counts occurrences of addr2 within addr1.
  7. 2swap 0 >r
  8. begin 2over search
  9. while 2over nip /string
  10. r> 1+ >r
  11. repeat 2drop 2drop r> ;
  12. : exchange ( a1 a2 -- )
  13. 2dup c@ swap c@ rot c! swap c! ;
  14. : reverse ( caddr u -- ) \ reverse a string
  15. 1- bounds begin 2dup > while
  16. 2dup exchange
  17. -1 /string
  18. repeat 2drop ;
  19. : sourcedir ( -- saddr su )
  20. \ Returns the directory in which the file
  21. \ invoking the word finds itself
  22. \ relative to gforth's execution directory.
  23. \ Useful for specifying in which dir to find
  24. \ specific files (e.g., public/, views/).
  25. sourcefilename \ get the name of our file
  26. pad dup >r place \ copy the string so we don't
  27. r> count \ modify sourcefilename.
  28. 2dup reverse \ reverse and search for first /
  29. s" /" search if \ if found, reverse string to
  30. 2dup reverse \ strip the filename but keep dir.
  31. else
  32. 2drop \ no slash,
  33. s" ./" \ same dir execution.
  34. then ;
  35. : file-exists? ( addr u -- addr u bool )
  36. 2dup file-status nip 0= ;
  37. : pubvar create 0 , 0 , ;
  38. \ User-defined routing
  39. wordlist constant routes
  40. pubvar reqroute
  41. : set-requested-route ( addr u -- )
  42. reqroute 2! ;
  43. : get-requested-route ( -- addr u )
  44. reqroute 2@ ;
  45. : fuzzy-find-route ( xt -- xt' bool )
  46. \ Takes an xt that accepts a name token
  47. \ and returns a bool.
  48. \ Traverse will run as long as xt
  49. \ returns true.
  50. \ Also takes the addr u of the requested
  51. \ route we're trying to validate.
  52. >r routes wordlist-id @ \ Store xt and specify wordlist
  53. begin
  54. dup
  55. while
  56. r@ over >r execute while r> @
  57. repeat r>
  58. then
  59. rdrop
  60. ?dup if
  61. name>int \ Get the xt of the nt.
  62. -1 \ Return true.
  63. else
  64. 0
  65. then ;
  66. : fuzzy-compare ( nt -- bool )
  67. \ Takes a route name token and returns
  68. \ whether that route name fuzzy matches
  69. \ the requested url
  70. name>string \ Get the string value of the NT.
  71. 2dup s" <" search if \ See if the route expects fuzzy matching.
  72. 2drop \ Drop search results.
  73. 2dup s" /" str-count >r \ Check to see if both routes have the same
  74. get-requested-route s" /" str-count \ number of / occurrences.
  75. r> = if
  76. 2dup s" <" str-count 0 do
  77. 2dup 2>r
  78. 2r@
  79. 2dup s" <" search drop \ Get position of "<",
  80. swap drop -
  81. swap drop
  82. get-requested-route rot /string \ crop until there in the requested route,
  83. 2dup s" /" search if \ search for the next / or end of route,
  84. swap drop -
  85. else
  86. 2drop
  87. then
  88. 2r@
  89. 2dup s" <" search drop \ and replace <...> with the requested route word
  90. swap drop -
  91. +s \ by prepending pre-< to route word
  92. 2r> s" >" search drop 1 /string \ and then by appending post-> to route word.
  93. s+
  94. 2>r
  95. 2drop
  96. 2r>
  97. loop
  98. get-requested-route compare \ Check to see if the strings match.
  99. else
  100. 2drop \ Drop name>string.
  101. -1 \ Keep looping.
  102. then
  103. else
  104. 2drop \ Drop search results.
  105. 2drop \ Drop name>string.
  106. -1 \ Keep looping.
  107. then ;
  108. : fuzzy-match ( addr u -- xt bool )
  109. set-requested-route
  110. ['] fuzzy-compare fuzzy-find-route ;
  111. : find-route ( addr u -- data )
  112. 2dup 2>r
  113. routes search-wordlist if
  114. 2rdrop \ Exact match found. Drop the dup string.
  115. >body @
  116. else
  117. 2r>
  118. fuzzy-match if \ Fuzzy match found.
  119. >body @
  120. else
  121. 0 \ No match at all.
  122. then
  123. then ;
  124. : register-route ( data addr u -- )
  125. 2dup routes search-wordlist if
  126. routes drop nip nip
  127. >body !
  128. else
  129. routes get-current >r set-current \ switch definition word lists
  130. nextname create ,
  131. r> set-current
  132. then ;
  133. \ Public directory
  134. pubvar public
  135. : set-public-path ( addr u -- )
  136. public 2! ;
  137. : get-public-path ( -- addr u )
  138. public 2@ ;
  139. sourcedir s" public" s+ set-public-path
  140. \ Views directory
  141. pubvar views
  142. : set-view-path ( addr u -- )
  143. views 2! ;
  144. : get-view-path ( -- addr u )
  145. views 2@ ;
  146. sourcedir s" views/" s+ set-view-path \ Needs that trailing slash
  147. \ Handling views
  148. pubvar viewoutput
  149. : set-view-output ( addr u -- )
  150. viewoutput 2! ;
  151. : get-view-output ( -- addr u )
  152. viewoutput 2@ ;
  153. : parse-view ( addr u -- )
  154. \ Get string between <$ $> and invoke `evaluate`.
  155. \ Append to viewoutput as we go.
  156. \ There's probably a better way of doing this
  157. \ but it works for me.
  158. begin
  159. 2dup s" <$" search if
  160. 2over swap 2>r \ If there is a match for <$, save addr and u.
  161. swap >r dup >r \ Store the match and output anything that
  162. - \ comes before it.
  163. get-view-output +s
  164. set-view-output
  165. r> r> swap \ Then reinstate the match "<$...".
  166. 2dup s" $>" search if \ Check to see if there's a closing tag $>.
  167. 2 - \ Add the close tag to the search result.
  168. swap drop \ save the end position of $>.
  169. dup >r
  170. - \ Reduce the string to <$ ... $>.
  171. evaluate \ Run user's code (maybe a bad idea?).
  172. r> \ Retrieve our saved end position of $>.
  173. r> r> \ Retrive the addr u from start of loop iter.
  174. rot \ Bring end $> to stack top.
  175. over >r \ Store the real string's length.
  176. - \ Subtract end $> from u to get the pos from top
  177. r> swap \ that we'd like to strip away. Restore saved u.
  178. /string \ Drop top of the string until the end of $>.
  179. 0 \ Keep looping.
  180. else
  181. get-view-output +s \ No closing tag. Just save the full string.
  182. set-view-output
  183. 2rdrop \ And drop the stored addr and u
  184. 2drop \ as well as both the 2dup we made before
  185. 2drop \ searching twice.
  186. -1 \ Exit the loop.
  187. then
  188. else
  189. 2drop \ No match for <$. Drop the 2dup from before search.
  190. get-view-output +s
  191. set-view-output \ Save string as-is to view output
  192. -1 \ exit the loop
  193. then
  194. until ;
  195. : render-view ( addr u -- vaddr vu ) \ Accepts a view filename. Returns parsed contents.
  196. s" " set-view-output
  197. get-view-path +s
  198. file-exists? if
  199. slurp-file
  200. parse-view
  201. else
  202. exit \ Continue to 404, no view.
  203. then
  204. get-view-output ;
  205. : <$ ( -- ) ; \ Do nothing.
  206. : $> ( -- ) ; \ Do nothing.
  207. : $type ( addr u -- ) \ User-land word for outputing via views.
  208. get-view-output +s
  209. set-view-output ;
  210. : import ( -- ) \ User-land word for including other view files.
  211. get-view-path +s
  212. file-exists? if
  213. slurp-file
  214. parse-view
  215. else
  216. s" "
  217. then ;
  218. \ Query params
  219. pubvar queryString
  220. : set-query-string ( addr u -- )
  221. queryString 2! ;
  222. : get-query-string ( -- addr u )
  223. queryString 2@ ;
  224. \ Request's Content-Type
  225. pubvar RequestContentType
  226. : set-content-type ( addr u -- )
  227. RequestContentType 2! ;
  228. : get-content-type ( -- addr u )
  229. RequestContentType 2@ ;
  230. : filetype: ( addr u "extension" -- ) \ takes a content-type and the extension
  231. create here over 1+ allot place
  232. does> count ;
  233. : get-filetype ( addr u -- caddr cu ) \ takes an extension, looks to find a definition
  234. find-name dup if
  235. name>int
  236. execute
  237. else
  238. drop
  239. s" text/plain"
  240. then ;
  241. s" text/plain" filetype: txt \ txt should always be defined
  242. s" text/html" filetype: html
  243. s" text/css" filetype: css
  244. s" text/javascript" filetype: js
  245. s" image/png" filetype: png
  246. s" image/gif" filetype: gif
  247. s" image/jpeg" filetype: jpg
  248. s" image/jpeg" filetype: jpeg
  249. s" image/x-icon" filetype: ico
  250. \ Internal request handling
  251. : HTTP/1.1 s" HTTP/1.1 " ;
  252. : response-status ( u -- addr u )
  253. dup case \ get status code info
  254. 200 of s" OK" endof
  255. 404 of s" Not Found" endof
  256. endcase
  257. s\" \n" s+
  258. rot s>d <# #s #> +s \ convert status code to string and prepend to info
  259. HTTP/1.1 +s ; \ prepend HTTP/1.1
  260. : content-type ( addr u -- caddr cu )
  261. s" Content-Type: " +s \ Prepend to the provided content type
  262. s\" \n\n" s+ ; \ Append 2 new lines
  263. : set-header ( u addr u -- raddr ru ) \ Accepts status code and content type string
  264. rot response-status \ Set response status
  265. 2swap content-type \ Set content-type
  266. s+ ; \ Join
  267. : read-request ( socket -- addr u )
  268. pad 4096 read-socket ;
  269. : send-response ( addr u socket -- )
  270. dup >r write-socket r> close-socket ;
  271. : store-query-string ( addr u -- raddr ru )
  272. 2dup s" ?" search if
  273. 2dup set-query-string \ store query string
  274. swap drop -
  275. else
  276. s" " set-query-string \ store empty query string (reset)
  277. 2drop
  278. then ;
  279. : requested-route ( addr u -- raddr ru )
  280. bl scan 1- swap 1+ swap
  281. 2dup bl scan swap drop - \ get the space-separated route
  282. store-query-string ; \ strip and store the query, leave route
  283. : .extension ( addr u -- addr u )
  284. 2dup reverse \ reverse the file name
  285. 2dup s" ." search \ search for the first occurance of "."
  286. if
  287. swap drop - \ remove the "." from the search results
  288. else
  289. s" txt"
  290. then
  291. 2dup reverse ; \ reverse reversed extension
  292. : serve-file-type ( addr u -- )
  293. .extension get-filetype set-content-type ;
  294. : serve-file ( addr u -- addr u )
  295. slurp-file ;
  296. : 404content-type txt ;
  297. : 404html s" 404";
  298. : either-resolve ( addr u -- resolveaddr resolveu )
  299. s" GET" search if
  300. s" html" get-filetype set-content-type \ reset the request's content-type
  301. requested-route
  302. 2dup find-route dup if
  303. >r 2drop r> \ keep xt, drop the route string
  304. execute \ execute the user's route handler
  305. else
  306. drop \ drop the null xt
  307. get-public-path +s \ see if route exists in public dir
  308. file-exists? if
  309. 2dup serve-file \ collect file contents
  310. 2swap serve-file-type \ set the file type
  311. else
  312. exit \ continue to 404
  313. then
  314. then
  315. 200 get-content-type set-header +s
  316. rdrop exit then ;
  317. : or-404 ( addr u -- 404addr 404u )
  318. 2drop
  319. 404 404content-type set-header 404html s+ ;
  320. : prepare-response ( addr u -- returnaddr returnu)
  321. either-resolve or-404 ;
  322. : start-server { server client }
  323. begin
  324. server 255 listen
  325. server accept-socket to client
  326. client read-request prepare-response client send-response
  327. again ;
  328. \ Userland
  329. : 1991: ( port -- )
  330. create-server 0 start-server ;
  331. : /1991 ( "<path> <word>" -- )
  332. bl word ' swap count register-route ;