A server-side web framework written in Forth. http://www.1-9-9-1.com
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

400 wiersze
15KB

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