sproutit-narwhal 0.1.106

Sign up to get free protection for your applications and to get access to all the features.
Files changed (291) hide show
  1. data/DISTRIBUTION.yml +15 -0
  2. data/README.md +86 -0
  3. data/Rakefile +349 -0
  4. data/VERSION.yml +7 -0
  5. data/bin/activate +50 -0
  6. data/bin/activate.bash +50 -0
  7. data/bin/activate.cmd +3 -0
  8. data/bin/js +67 -0
  9. data/bin/json +2 -0
  10. data/bin/narwhal +67 -0
  11. data/bin/narwhal.cmd +29 -0
  12. data/bin/sea +45 -0
  13. data/bin/sea.cmd +25 -0
  14. data/bin/tusk +2 -0
  15. data/bin/tusk.cmd +5 -0
  16. data/catalog.json +902 -0
  17. data/docs/available-packages.md +32 -0
  18. data/docs/browser-api-plan.md +290 -0
  19. data/docs/browser-api.md +153 -0
  20. data/docs/download.md +25 -0
  21. data/docs/engines.md +32 -0
  22. data/docs/json-tool.md +121 -0
  23. data/docs/lib/binary.wiki +242 -0
  24. data/docs/lib/file.wiki +325 -0
  25. data/docs/lib/os/popen.md +70 -0
  26. data/docs/modules.md +38 -0
  27. data/docs/narwhal.md +487 -0
  28. data/docs/packages-howto.md +32 -0
  29. data/docs/packages.md +30 -0
  30. data/docs/posts/2009-07-29-hello-0.1.md +19 -0
  31. data/docs/quick-start.md +69 -0
  32. data/docs/sea.md +49 -0
  33. data/engines/browser/lib/binary.js +2 -0
  34. data/engines/browser/lib/reactor.js +21 -0
  35. data/engines/browser/lib/system.js +3 -0
  36. data/engines/default/lib/array.js +164 -0
  37. data/engines/default/lib/binary-engine.js +53 -0
  38. data/engines/default/lib/binary.js +755 -0
  39. data/engines/default/lib/date.js +8 -0
  40. data/engines/default/lib/file-engine.js +119 -0
  41. data/engines/default/lib/function.js +119 -0
  42. data/engines/default/lib/global.js +11 -0
  43. data/engines/default/lib/io-engine.js +26 -0
  44. data/engines/default/lib/json.js +488 -0
  45. data/engines/default/lib/object.js +69 -0
  46. data/engines/default/lib/os-engine.js +3 -0
  47. data/engines/default/lib/reactor.js +12 -0
  48. data/engines/default/lib/string.js +84 -0
  49. data/engines/default/lib/system.js +20 -0
  50. data/engines/default/lib/worker.js +133 -0
  51. data/engines/jsc/README.md +18 -0
  52. data/engines/jsc/bootstrap.js +53 -0
  53. data/engines/jsc/deps/http-parser/LICENSE +77 -0
  54. data/engines/jsc/deps/http-parser/README.md +145 -0
  55. data/engines/jsc/deps/http-parser/http_parser.c +6087 -0
  56. data/engines/jsc/deps/http-parser/http_parser.h +141 -0
  57. data/engines/jsc/deps/http-parser/http_parser.rl +500 -0
  58. data/engines/jsc/deps/http-parser/test.c +858 -0
  59. data/engines/jsc/include/binary-engine.h +11 -0
  60. data/engines/jsc/include/io-engine.h +23 -0
  61. data/engines/jsc/include/narwhal.h +427 -0
  62. data/engines/jsc/lib/file-engine.js +31 -0
  63. data/engines/jsc/lib/http.js +1 -0
  64. data/engines/jsc/lib/io-engine.js +202 -0
  65. data/engines/jsc/lib/os-engine.js +25 -0
  66. data/engines/jsc/lib/system.js +18 -0
  67. data/engines/jsc/lib/zip.js +1 -0
  68. data/engines/jsc/narwhal-jsc.c +273 -0
  69. data/engines/jsc/narwhal.c +29 -0
  70. data/engines/jsc/package.json +8 -0
  71. data/engines/jsc/src/binary-engine.cc +290 -0
  72. data/engines/jsc/src/file-engine.cc +405 -0
  73. data/engines/jsc/src/io-engine.cc +423 -0
  74. data/engines/jsc/src/jack/handler/jill.cc +710 -0
  75. data/engines/jsc/src/os-engine.cc +210 -0
  76. data/engines/rhino/bin/narwhal-rhino +68 -0
  77. data/engines/rhino/bin/narwhal-rhino.cmd +34 -0
  78. data/engines/rhino/bootstrap.js +119 -0
  79. data/engines/rhino/jars/jline.jar +0 -0
  80. data/engines/rhino/jars/jna.jar +0 -0
  81. data/engines/rhino/jars/js.jar +0 -0
  82. data/engines/rhino/lib/binary-engine.js +83 -0
  83. data/engines/rhino/lib/concurrency.js +6 -0
  84. data/engines/rhino/lib/event-queue.js +18 -0
  85. data/engines/rhino/lib/file-engine.js +216 -0
  86. data/engines/rhino/lib/http-client-engine.js +90 -0
  87. data/engines/rhino/lib/http-engine.js +10 -0
  88. data/engines/rhino/lib/io-engine.js +347 -0
  89. data/engines/rhino/lib/md5-engine.js +40 -0
  90. data/engines/rhino/lib/os-engine.js +150 -0
  91. data/engines/rhino/lib/packages-engine.js +71 -0
  92. data/engines/rhino/lib/sandbox-engine.js +70 -0
  93. data/engines/rhino/lib/system.js +38 -0
  94. data/engines/rhino/lib/worker-engine.js +23 -0
  95. data/engines/rhino/lib/zip.js +78 -0
  96. data/engines/rhino/package.json +4 -0
  97. data/engines/secure/lib/file.js +6 -0
  98. data/engines/secure/lib/system.js +6 -0
  99. data/engines/template/bin/narwhal-engine-name +32 -0
  100. data/engines/template/bootstrap.js +40 -0
  101. data/engines/template/lib/file-engine.js +118 -0
  102. data/engines/template/lib/system.js +17 -0
  103. data/examples/browser-deployment-jackconfig.js +35 -0
  104. data/examples/fibonacci-worker.js +35 -0
  105. data/examples/fibonacci.js +19 -0
  106. data/examples/hello +2 -0
  107. data/examples/narwhal +3 -0
  108. data/examples/not-quite-a-quine.js +1 -0
  109. data/extconf.rb +44 -0
  110. data/gem_bin/narwhal +5 -0
  111. data/gem_bin/sea +4 -0
  112. data/gem_bin/tusk +4 -0
  113. data/lib/args.js +849 -0
  114. data/lib/base16.js +16 -0
  115. data/lib/base64.js +120 -0
  116. data/lib/codec/base64.js +8 -0
  117. data/lib/crc32.js +60 -0
  118. data/lib/file-bootstrap.js +187 -0
  119. data/lib/file.js +659 -0
  120. data/lib/hash.js +28 -0
  121. data/lib/hashp.js +65 -0
  122. data/lib/html.js +16 -0
  123. data/lib/http-client.js +134 -0
  124. data/lib/http.js +17 -0
  125. data/lib/io.js +98 -0
  126. data/lib/jsmin.js +315 -0
  127. data/lib/jsonpath.js +89 -0
  128. data/lib/logger.js +55 -0
  129. data/lib/md4.js +146 -0
  130. data/lib/md5.js +164 -0
  131. data/lib/mime.js +166 -0
  132. data/lib/narwhal.js +102 -0
  133. data/lib/narwhal/client.js +261 -0
  134. data/lib/narwhal/compile.js +99 -0
  135. data/lib/narwhal/env.js +140 -0
  136. data/lib/narwhal/inline.js +106 -0
  137. data/lib/narwhal/json.js +324 -0
  138. data/lib/narwhal/json.md +178 -0
  139. data/lib/narwhal/repl.js +96 -0
  140. data/lib/narwhal/server-test.js +6 -0
  141. data/lib/narwhal/server.js +270 -0
  142. data/lib/narwhal/tusk.js +170 -0
  143. data/lib/narwhal/tusk/bin.js +13 -0
  144. data/lib/narwhal/tusk/bundle.js +0 -0
  145. data/lib/narwhal/tusk/catalog.js +22 -0
  146. data/lib/narwhal/tusk/clone.js +66 -0
  147. data/lib/narwhal/tusk/consolidate.js +25 -0
  148. data/lib/narwhal/tusk/create-catalog.js +80 -0
  149. data/lib/narwhal/tusk/engine.js +42 -0
  150. data/lib/narwhal/tusk/freeze.js +0 -0
  151. data/lib/narwhal/tusk/init.js +56 -0
  152. data/lib/narwhal/tusk/install.js +288 -0
  153. data/lib/narwhal/tusk/list.js +20 -0
  154. data/lib/narwhal/tusk/orphans.js +0 -0
  155. data/lib/narwhal/tusk/reheat.js +15 -0
  156. data/lib/narwhal/tusk/remove.js +15 -0
  157. data/lib/narwhal/tusk/search.js +145 -0
  158. data/lib/narwhal/tusk/update.js +21 -0
  159. data/lib/narwhal/tusk/upgrade.js +0 -0
  160. data/lib/os.js +33 -0
  161. data/lib/packages.js +423 -0
  162. data/lib/printf.js +169 -0
  163. data/lib/promise.js +352 -0
  164. data/lib/querystring.js +176 -0
  165. data/lib/ref-send.js +257 -0
  166. data/lib/regexp.js +12 -0
  167. data/lib/sandbox.js +422 -0
  168. data/lib/sha.js +112 -0
  169. data/lib/sha256.js +102 -0
  170. data/lib/struct.js +228 -0
  171. data/lib/term.js +179 -0
  172. data/lib/test/assert.js +95 -0
  173. data/lib/test/equiv.js +188 -0
  174. data/lib/test/jsdump.js +165 -0
  175. data/lib/test/runner.js +129 -0
  176. data/lib/unload.js +13 -0
  177. data/lib/uri.js +378 -0
  178. data/lib/url.js +5 -0
  179. data/lib/utf8.js +64 -0
  180. data/lib/util.js +985 -0
  181. data/lib/uuid.js +89 -0
  182. data/lib/xregexp.js +521 -0
  183. data/local.json.template +1 -0
  184. data/narwhal.gemspec +105 -0
  185. data/narwhal.js +213 -0
  186. data/package.json +26 -0
  187. data/packages/readline/engines/default/lib/readline.js +4 -0
  188. data/packages/readline/engines/rhino/lib/readline.js +6 -0
  189. data/packages/readline/package.json +5 -0
  190. data/sources.json +207 -0
  191. data/tests/all-tests.js +17 -0
  192. data/tests/args.js +31 -0
  193. data/tests/args/domain.js +215 -0
  194. data/tests/args/options.js +36 -0
  195. data/tests/args/shifting.js +92 -0
  196. data/tests/args/validation.js +31 -0
  197. data/tests/base64.js +23 -0
  198. data/tests/commonjs.js +3 -0
  199. data/tests/commonjs/all-tests.js +12 -0
  200. data/tests/commonjs/bytearray-encodings-tests.js +69 -0
  201. data/tests/commonjs/bytearray-tests.js +465 -0
  202. data/tests/commonjs/bytestring-encodings-tests.js +89 -0
  203. data/tests/commonjs/bytestring-tests.js +263 -0
  204. data/tests/commonjs/es5/all-tests.js +3 -0
  205. data/tests/commonjs/es5/bind.js +29 -0
  206. data/tests/commonjs/file-tests.js +315 -0
  207. data/tests/commonjs/file/dirname.js +31 -0
  208. data/tests/commonjs/file/extension.js +45 -0
  209. data/tests/commonjs/file/is-absolute.js +11 -0
  210. data/tests/commonjs/file/iterator.js +101 -0
  211. data/tests/commonjs/file/normal.js +27 -0
  212. data/tests/commonjs/file/path.js +17 -0
  213. data/tests/commonjs/file/relative.js +42 -0
  214. data/tests/commonjs/file/resolve.js +44 -0
  215. data/tests/commonjs/module-tests.js +9 -0
  216. data/tests/commonjs/modules/absolute/b.js +1 -0
  217. data/tests/commonjs/modules/absolute/program.js +5 -0
  218. data/tests/commonjs/modules/absolute/submodule/a.js +3 -0
  219. data/tests/commonjs/modules/absolute/test.js +9 -0
  220. data/tests/commonjs/modules/all-tests.js +47 -0
  221. data/tests/commonjs/modules/config.js +11 -0
  222. data/tests/commonjs/modules/cyclic/a.js +4 -0
  223. data/tests/commonjs/modules/cyclic/b.js +4 -0
  224. data/tests/commonjs/modules/cyclic/program.js +10 -0
  225. data/tests/commonjs/modules/cyclic/test.js +9 -0
  226. data/tests/commonjs/modules/determinism/program.js +3 -0
  227. data/tests/commonjs/modules/determinism/submodule/a.js +8 -0
  228. data/tests/commonjs/modules/determinism/submodule/b.js +2 -0
  229. data/tests/commonjs/modules/determinism/test.js +9 -0
  230. data/tests/commonjs/modules/exactExports/a.js +3 -0
  231. data/tests/commonjs/modules/exactExports/program.js +4 -0
  232. data/tests/commonjs/modules/exactExports/test.js +9 -0
  233. data/tests/commonjs/modules/hasOwnProperty/hasOwnProperty.js +0 -0
  234. data/tests/commonjs/modules/hasOwnProperty/program.js +3 -0
  235. data/tests/commonjs/modules/hasOwnProperty/test.js +9 -0
  236. data/tests/commonjs/modules/hasOwnProperty/toString.js +0 -0
  237. data/tests/commonjs/modules/method/a.js +12 -0
  238. data/tests/commonjs/modules/method/program.js +8 -0
  239. data/tests/commonjs/modules/method/test.js +9 -0
  240. data/tests/commonjs/modules/missing/program.js +8 -0
  241. data/tests/commonjs/modules/missing/test.js +9 -0
  242. data/tests/commonjs/modules/monkeys/a.js +1 -0
  243. data/tests/commonjs/modules/monkeys/program.js +4 -0
  244. data/tests/commonjs/modules/monkeys/test.js +9 -0
  245. data/tests/commonjs/modules/nested/a/b/c/d.js +3 -0
  246. data/tests/commonjs/modules/nested/program.js +3 -0
  247. data/tests/commonjs/modules/nested/test.js +9 -0
  248. data/tests/commonjs/modules/relative/program.js +5 -0
  249. data/tests/commonjs/modules/relative/submodule/a.js +1 -0
  250. data/tests/commonjs/modules/relative/submodule/b.js +2 -0
  251. data/tests/commonjs/modules/relative/test.js +9 -0
  252. data/tests/commonjs/modules/transitive/a.js +1 -0
  253. data/tests/commonjs/modules/transitive/b.js +1 -0
  254. data/tests/commonjs/modules/transitive/c.js +3 -0
  255. data/tests/commonjs/modules/transitive/program.js +3 -0
  256. data/tests/commonjs/modules/transitive/test.js +9 -0
  257. data/tests/file/all-tests.js +61 -0
  258. data/tests/file/fnmatch.js +102 -0
  259. data/tests/file/glob.js +466 -0
  260. data/tests/file/match.js +102 -0
  261. data/tests/global.js +6 -0
  262. data/tests/global/array.js +19 -0
  263. data/tests/hashes.js +94 -0
  264. data/tests/html.js +13 -0
  265. data/tests/io/stringio.js +21 -0
  266. data/tests/os/all-tests.js +4 -0
  267. data/tests/os/popen.js +41 -0
  268. data/tests/os/system.js +22 -0
  269. data/tests/printf.js +123 -0
  270. data/tests/query-string.js +87 -0
  271. data/tests/sandbox/byte-io.js +20 -0
  272. data/tests/sandbox/fileName.js +3 -0
  273. data/tests/sandbox/foo.js +0 -0
  274. data/tests/sandbox/reload.js +79 -0
  275. data/tests/string.js +35 -0
  276. data/tests/uri.js +41 -0
  277. data/tests/util/all-tests.js +79 -0
  278. data/tests/util/array.js +207 -0
  279. data/tests/util/array/is-arguments.js +29 -0
  280. data/tests/util/array/is-array-like.js +29 -0
  281. data/tests/util/case.js +9 -0
  282. data/tests/util/collection.js +104 -0
  283. data/tests/util/eq.js +57 -0
  284. data/tests/util/expand.js +45 -0
  285. data/tests/util/object.js +125 -0
  286. data/tests/util/operator.js +25 -0
  287. data/tests/util/range.js +19 -0
  288. data/tests/util/repr.js +26 -0
  289. data/tests/util/string.js +34 -0
  290. data/tests/util/unique.js +12 -0
  291. metadata +434 -0
@@ -0,0 +1,423 @@
1
+ #include <narwhal.h>
2
+
3
+ #include <iconv.h>
4
+ #include <stdlib.h>
5
+ #include <sys/types.h>
6
+ #include <sys/uio.h>
7
+ #include <unistd.h>
8
+ #include <string.h>
9
+ #include <errno.h>
10
+
11
+ #include <io-engine.h>
12
+ #include <binary-engine.h>
13
+
14
+ CONSTRUCTOR(IO_constructor)
15
+ {
16
+ IOPrivate *data = (IOPrivate*)malloc(sizeof(IOPrivate));
17
+ data->input = -1;
18
+ data->output = -1;
19
+
20
+ if (ARGC > 0) {
21
+ ARGN_INT(in_fd, 0);
22
+ data->input = in_fd;
23
+ }
24
+ if (ARGC > 1) {
25
+ ARGN_INT(out_fd, 1);
26
+ data->output = out_fd;
27
+ }
28
+
29
+ DEBUG("io=[%d,%d]\n", data->input, data->output);
30
+
31
+ #ifdef NARWHAL_JSC
32
+ JSObjectRef object = JSObjectMake(_context, IO_class(_context), (void*)data);
33
+ #elif NARWHAL_V8
34
+ NWObject object = THIS;
35
+ SET_INTERNAL(object, data);
36
+ #endif
37
+
38
+ return object;
39
+ }
40
+ END
41
+
42
+ FUNCTION(IO_readInto, ARG_OBJ(buffer), ARG_INT(length))
43
+ {
44
+ GET_INTERNAL(IOPrivate*, data, THIS);
45
+
46
+ int fd = data->input;
47
+ if (fd < 0)
48
+ THROW("Stream not open for reading.");
49
+
50
+ // get the _bytes property of the ByteString/Array, and the private data of that
51
+ NWObject _bytes = GET_OBJECT(buffer, "_bytes");
52
+ GET_INTERNAL(BytesPrivate*, bytes, _bytes);
53
+
54
+ int offset = GET_INT(buffer, "_offset");
55
+ if (ARGC > 2) {
56
+ ARGN_INT(from, 2);
57
+ if (from < 0)
58
+ THROW("Tried to read out of range.");
59
+ offset += from;
60
+ }
61
+
62
+ // FIXME: make SURE this is correct
63
+ if (offset < 0 || length > bytes->length - offset)
64
+ THROW("FIXME: Buffer too small. Throw or truncate?");
65
+
66
+ ssize_t total = 0,
67
+ bytesRead = 0;
68
+
69
+ while (total < length) {
70
+ bytesRead = read(fd, bytes->buffer + (offset + total), length - total);
71
+ DEBUG("bytesRead=%d (length - total)=%d\n", bytesRead, length - total);
72
+ if (bytesRead <= 0)
73
+ break;
74
+ total += bytesRead;
75
+ }
76
+
77
+ return JS_int(total);
78
+ }
79
+ END
80
+
81
+ FUNCTION(IO_writeInto, ARG_OBJ(buffer), ARG_INT(from), ARG_INT(to))
82
+ {
83
+ GET_INTERNAL(IOPrivate*, data, THIS);
84
+
85
+ int fd = data->output;
86
+ if (fd < 0)
87
+ THROW("Stream not open for writing.");
88
+
89
+ // get the _bytes property of the ByteString/Array, and the private data of that
90
+ NWObject _bytes = GET_OBJECT(buffer, "_bytes");
91
+ GET_INTERNAL(BytesPrivate*, bytes, _bytes);
92
+
93
+ int offset = GET_INT(buffer, "_offset");
94
+ int length = GET_INT(buffer, "_length");
95
+
96
+ // FIXME: make SURE this is correct
97
+ if (offset < 0 || from < 0 || (offset + to) > bytes->length)
98
+ THROW("Tried to write out of range.");
99
+
100
+ write(fd, bytes->buffer + (offset + from), (to - from));
101
+
102
+ return JS_undefined;
103
+ }
104
+ END
105
+
106
+ FUNCTION(IO_flush)
107
+ {
108
+ // FIXME
109
+ return THIS;
110
+ }
111
+ END
112
+
113
+ FUNCTION(IO_close)
114
+ {
115
+ GET_INTERNAL(IOPrivate*, data, THIS);
116
+
117
+ if (data->input >= 0)
118
+ close(data->input);
119
+ if (data->output >= 0)
120
+ close(data->output);
121
+
122
+ data->input = -1;
123
+ data->output = -1;
124
+
125
+ return JS_undefined;
126
+ }
127
+ END
128
+
129
+ DESTRUCTOR(IO_finalize)
130
+ {
131
+ GET_INTERNAL(IOPrivate*, data, object);
132
+
133
+ DEBUG("freeing io=[%d,%d]\n", data->input, data->output);
134
+
135
+ if (data->input >= 0)
136
+ close(data->input);
137
+ if (data->output >= 0)
138
+ close(data->output);
139
+
140
+ free(data);
141
+ }
142
+ END
143
+
144
+ CONSTRUCTOR(TextInputStream_constructor)
145
+ {
146
+ TextInputStreamPrivate *data = (TextInputStreamPrivate*)malloc(sizeof(TextInputStreamPrivate));
147
+
148
+ //raw, lineBuffering, buffering, charset, options
149
+ ARGN_OBJ(raw, 0);
150
+ //ARGN_OBJ(lineBuffering, 1);
151
+ //ARGN_OBJ(buffering, 2);
152
+
153
+ NWString charsetStr = (ARGC > 3 && IS_STRING(ARGV(3))) ?
154
+ TO_STRING(ARGV(3)) :
155
+ JS_str_utf8("UTF-8", strlen("UTF-8"));
156
+
157
+ //ARGN_OBJ(options, 4);
158
+
159
+ data->inBuffer = (char*)malloc(1024);
160
+ data->inBufferSize = 1024;
161
+ data->inBufferUsed = 0;
162
+
163
+ GET_INTERNAL(IOPrivate*, raw_data, raw);
164
+ data->input = raw_data->input;
165
+
166
+ #ifdef NARWHAL_JSC
167
+ JSObjectRef object = JSObjectMake(_context, TextInputStream_class(_context), (void*)data);
168
+ #elif NARWHAL_V8
169
+ NWObject object = THIS;
170
+ SET_INTERNAL(object, data);
171
+ #endif
172
+
173
+ SET_VALUE(object, "raw", raw);
174
+ SET_VALUE(object, "charset", charsetStr);
175
+
176
+ GET_UTF8(charset, charsetStr);
177
+ data->cd = iconv_open(UTF_16_ENCODING, charset);
178
+ if (data->cd == (iconv_t)-1)
179
+ THROW("Error TextInputStream (iconv_open)");
180
+
181
+ return object;
182
+ }
183
+ END
184
+
185
+ DESTRUCTOR(TextInputStream_finalize)
186
+ {
187
+ GET_INTERNAL(TextInputStreamPrivate*, data, object);
188
+
189
+ if (data) {
190
+ iconv_close(data->cd);
191
+ if (data->inBuffer)
192
+ free(data->inBuffer);
193
+ free(data);
194
+ }
195
+ }
196
+ END
197
+
198
+ FUNCTION(TextInputStream_read)
199
+ {
200
+ size_t numChars = 0;
201
+ if (ARGC > 0) {
202
+ ARGN_INT(n, 0);
203
+ numChars = n;
204
+ }
205
+
206
+ GET_INTERNAL(TextInputStreamPrivate*, d, THIS);
207
+ int fd = d->input;
208
+ iconv_t cd = d->cd;
209
+
210
+ size_t outBufferSize = numChars ? numChars * sizeof(NWChar) : 1024;
211
+ size_t outBufferUsed = 0;
212
+ char *outBuffer = (char*)malloc(outBufferSize);
213
+
214
+ while (true) {
215
+ // if the outBuffer is completely filled, double it's size
216
+ if (outBufferUsed >= outBufferSize) {
217
+ outBufferSize *= 2;
218
+ DEBUG("reallocing: %d (maybe: %d)\n", outBufferSize, numChars);
219
+
220
+ // if we were given a number of characters to read then we've read them all, so we're done
221
+ if (numChars != 0)
222
+ break;
223
+
224
+ outBuffer = (char*)realloc(outBuffer, outBufferSize);
225
+ }
226
+
227
+ // if there's no data in the buffer read some more
228
+ if (d->inBufferUsed == 0) {
229
+ DEBUG("nothing in inBuffer, reading more\n");
230
+ size_t num = read(fd, d->inBuffer + d->inBufferUsed, d->inBufferSize - d->inBufferUsed);
231
+ if (num >= 0)
232
+ d->inBufferUsed += num;
233
+ }
234
+
235
+ // still no data to read, so stop
236
+ if (d->inBufferUsed == 0) {
237
+ DEBUG("still nothing in inBuffer, done reading for now\n");
238
+ break;
239
+ }
240
+
241
+ char *in = d->inBuffer;
242
+ size_t inLeft = d->inBufferUsed;
243
+ char *out = outBuffer + outBufferUsed;
244
+ size_t outLeft = outBufferSize - outBufferUsed;
245
+
246
+ size_t ret = iconv(cd, &in, &inLeft, &out, &outLeft);
247
+ if (ret != (size_t)-1 || errno == EINVAL || errno == E2BIG) {
248
+ if (inLeft) {
249
+ DEBUG("shifting %d bytes down by %d (had %d)\n", inLeft, in - d->inBuffer, d->inBufferUsed);
250
+ memmove(d->inBuffer, in, inLeft);
251
+ }
252
+ d->inBufferUsed = inLeft;
253
+
254
+ outBufferUsed = outBufferSize - outLeft;
255
+ if (outBufferUsed != (out - outBuffer)) printf("sanity check failed: %d %d\n", outBufferUsed, (out - outBuffer));
256
+ } else if (errno == EILSEQ) {
257
+ // TODO: gracefully handle this case.
258
+ // Illegal character or shift sequence
259
+ free(outBuffer);
260
+ THROW("Conversion error (Illegal character or shift sequence)");
261
+ } else if (errno == EBADF) {
262
+ // Invalid conversion descriptor
263
+ free(outBuffer);
264
+ THROW("Conversion error (Invalid conversion descriptor)");
265
+ } else {
266
+ // This errno is not defined
267
+ free(outBuffer);
268
+ THROW("Conversion error (unknown)");
269
+ }
270
+ }
271
+
272
+ /*
273
+ FILE *tmp = fopen("tmp.utf16", "w");
274
+ fwrite(outBuffer, 1, outBufferUsed, tmp);
275
+ fclose(tmp);
276
+ //*/
277
+
278
+ NWValue result = JS_str_utf16(outBuffer, outBufferUsed);
279
+ free(outBuffer);
280
+
281
+ return result;
282
+ }
283
+ END
284
+
285
+ /*
286
+ FUNCTION(TextInputStream_readLine)
287
+ {
288
+ }
289
+ END
290
+
291
+ FUNCTION(TextInputStream_next)
292
+ {
293
+ }
294
+ END
295
+
296
+ FUNCTION(TextInputStream_iterator)
297
+ {
298
+ }
299
+ END
300
+
301
+ FUNCTION(TextInputStream_forEach, ARG_FN(block), ARG_OBJ(context))
302
+ {
303
+ }
304
+ END
305
+
306
+ FUNCTION(TextInputStream_input)
307
+ {
308
+ }
309
+ END
310
+
311
+ FUNCTION(TextInputStream_readLines)
312
+ {
313
+ }
314
+ END
315
+
316
+ FUNCTION(TextInputStream_readInto, ARG_OBJ(buffer))
317
+ {
318
+ }
319
+ END
320
+
321
+ FUNCTION(TextInputStream_copy, ARG_OBJ(output), ARG_OBJ(mode), ARG_OBJ(options))
322
+ {
323
+ }
324
+ END
325
+
326
+ FUNCTION(TextInputStream_close)
327
+ {
328
+ }
329
+ END
330
+ */
331
+
332
+ #ifdef NARWHAL_JSC
333
+
334
+ extern "C" JSClassRef IO_class(JSContextRef _context)
335
+ {
336
+ static JSClassRef jsClass;
337
+ if (!jsClass)
338
+ {
339
+ JSStaticFunction staticFuctions[5] = {
340
+ { "readInto", IO_readInto, kJSPropertyAttributeNone },
341
+ { "writeInto", IO_writeInto, kJSPropertyAttributeNone },
342
+ { "flush", IO_flush, kJSPropertyAttributeNone },
343
+ { "close", IO_close, kJSPropertyAttributeNone },
344
+ { NULL, NULL, NULL }
345
+ };
346
+
347
+ JSClassDefinition definition = kJSClassDefinitionEmpty;
348
+ definition.className = "IO";
349
+ definition.staticFunctions = staticFuctions;
350
+ definition.finalize = IO_finalize;
351
+ definition.callAsConstructor = IO_constructor;
352
+
353
+ jsClass = JSClassCreate(&definition);
354
+ }
355
+
356
+ return jsClass;
357
+ }
358
+
359
+ extern "C" JSClassRef TextInputStream_class(JSContextRef _context)
360
+ {
361
+ static JSClassRef jsClass;
362
+ if (!jsClass)
363
+ {
364
+ JSStaticFunction staticFuctions[] = {
365
+ { "read", TextInputStream_read, kJSPropertyAttributeNone },
366
+ //{ "readLine", TextInputStream_readLine, kJSPropertyAttributeNone },
367
+ //{ "next", TextInputStream_next, kJSPropertyAttributeNone },
368
+ //{ "iterator", TextInputStream_iterator, kJSPropertyAttributeNone },
369
+ //{ "forEach", TextInputStream_forEach, kJSPropertyAttributeNone },
370
+ //{ "input", TextInputStream_input, kJSPropertyAttributeNone },
371
+ //{ "readLines", TextInputStream_readLines, kJSPropertyAttributeNone },
372
+ //{ "readInto", TextInputStream_readInto, kJSPropertyAttributeNone },
373
+ //{ "copy", TextInputStream_copy, kJSPropertyAttributeNone },
374
+ //{ "close", TextInputStream_close, kJSPropertyAttributeNone },
375
+ { NULL, NULL, NULL }
376
+ };
377
+
378
+ JSClassDefinition definition = kJSClassDefinitionEmpty;
379
+ definition.className = "TextInputStream";
380
+ definition.staticFunctions = staticFuctions;
381
+ definition.finalize = TextInputStream_finalize;
382
+ definition.callAsConstructor = TextInputStream_constructor;
383
+
384
+ jsClass = JSClassCreate(&definition);
385
+ }
386
+
387
+ return jsClass;
388
+ }
389
+
390
+ #elif NARWHAL_V8
391
+
392
+ NWObject TextInputStream_class() {
393
+ v8::Handle<v8::FunctionTemplate> ft = v8::FunctionTemplate::New(TextInputStream_constructor);
394
+ v8::Handle<v8::ObjectTemplate> ot = ft->InstanceTemplate();
395
+ ot->Set("read", v8::FunctionTemplate::New(TextInputStream_read));
396
+ ot->SetInternalFieldCount(1);
397
+ return ft->GetFunction();
398
+ }
399
+
400
+ NWObject IO_class() {
401
+ v8::Handle<v8::FunctionTemplate> ft = v8::FunctionTemplate::New(IO_constructor);
402
+ v8::Handle<v8::ObjectTemplate> ot = ft->InstanceTemplate();
403
+ ot->Set("readInto", v8::FunctionTemplate::New(IO_readInto));
404
+ ot->Set("writeInto", v8::FunctionTemplate::New(IO_writeInto));
405
+ ot->Set("flush", v8::FunctionTemplate::New(IO_flush));
406
+ ot->Set("close", v8::FunctionTemplate::New(IO_close));
407
+ ot->SetInternalFieldCount(1);
408
+ return ft->GetFunction();
409
+ }
410
+ #endif
411
+
412
+ NARWHAL_MODULE(io_engine)
413
+ {
414
+ EXPORTS("IO", CLASS(IO));
415
+ EXPORTS("TextInputStream", CLASS(TextInputStream));
416
+
417
+ EXPORTS("STDIN_FILENO", JS_int(STDIN_FILENO));
418
+ EXPORTS("STDOUT_FILENO", JS_int(STDOUT_FILENO));
419
+ EXPORTS("STDERR_FILENO", JS_int(STDERR_FILENO));
420
+
421
+ require("io-engine.js");
422
+ }
423
+ END_NARWHAL_MODULE
@@ -0,0 +1,710 @@
1
+ /*
2
+ * Jill: Jack's companion. A minimal JSGI compatible webserver.
3
+ *
4
+ * Created by Thomas Robinson.
5
+ * Copyright 2009, 280 North, Inc.
6
+ *
7
+ * Known issues:
8
+ *
9
+ * - Buffers entire request body before dispatching
10
+ * - Stalls if Content-Length is greater than bytes sent (needs timeout)
11
+ * - "Expect: 100-continue" not handled
12
+ * - Keepalive not supported
13
+ *
14
+ */
15
+
16
+ #include <narwhal.h>
17
+
18
+ #include <arpa/inet.h>
19
+ #include <netinet/in.h>
20
+ #include <sys/socket.h>
21
+ #include <unistd.h>
22
+
23
+ #include "../../../deps/http-parser/http_parser.h"
24
+
25
+ #include <binary-engine.h>
26
+ #include <io-engine.h>
27
+
28
+ NWObject ByteIO;
29
+ NWObject ByteString;
30
+
31
+ typedef struct _client_data {
32
+ int fd;
33
+ size_t len;
34
+ char *buf;
35
+
36
+ JSContextRef _context;
37
+ JSValueRef *_exception;
38
+
39
+ NWObject env;
40
+ NWObject app;
41
+
42
+ char *base;
43
+
44
+ // char *path;
45
+ ssize_t pathOff;
46
+ ssize_t pathLen;
47
+
48
+ // char *query;
49
+ ssize_t queryOff;
50
+ ssize_t queryLen;
51
+
52
+ // char *hName;
53
+ ssize_t hNameOff;
54
+ ssize_t hNameLen;
55
+
56
+ // char *hValue;
57
+ ssize_t hValueOff;
58
+ ssize_t hValueLen;
59
+
60
+ //char *body;
61
+ ssize_t bodyOff;
62
+ ssize_t bodyLen;
63
+
64
+ NWString SERVER_NAME;
65
+ NWString SERVER_PORT;
66
+
67
+ int complete;
68
+ } client_data;
69
+
70
+ NWValue processHeader(
71
+ JSContextRef _context, JSValueRef *_exception,
72
+ client_data *data
73
+ ) {
74
+ if (data->hValueLen > 0) {
75
+ NWObject env = data->env;
76
+
77
+ char *hName = data->base + data->hNameOff;
78
+ char *hValue = data->base + data->hValueOff;
79
+
80
+ hName[data->hNameLen] = '\0';
81
+ hValue[data->hValueLen] = '\0';
82
+
83
+ char buffer[data->hNameLen + 5];
84
+ memcpy(buffer, "HTTP_", 5);
85
+ char c, *dst = buffer + 5, *src = hName;
86
+ while (c = *(src++))
87
+ *(dst++) = (c == '-') ? '_' : toupper(c);
88
+ *dst = '\0';
89
+
90
+ if (!strcmp("HTTP_CONTENT_LENGTH", buffer) || !strcmp("HTTP_CONTENT_TYPE", buffer)) {
91
+ SET_VALUE(env, buffer + 5, JS_str_utf8(hValue, data->hValueLen));
92
+ } else {
93
+ SET_VALUE(env, buffer, JS_str_utf8(hValue, data->hValueLen));
94
+
95
+ if (!strcmp("HTTP_HOST", buffer)) {
96
+ // if there's a colon we can split into SERVER_NAME and SERVER_PORT
97
+ char *colonPtr = (char *)memchr(hValue, ':', data->hValueLen);
98
+ if (colonPtr) {
99
+ *colonPtr = '\0';
100
+ data->SERVER_NAME = JS_str_utf8(hValue, colonPtr - hValue);
101
+ data->SERVER_PORT = JS_str_utf8(colonPtr + 1, data->hValueLen - (colonPtr + 1));
102
+ *colonPtr = ':'; // restore colon
103
+ } else {
104
+ data->SERVER_NAME = JS_str_utf8(hValue, data->hValueLen);
105
+ }
106
+ }
107
+ }
108
+
109
+ data->hNameOff = -1;
110
+ data->hNameLen = 0;
111
+ data->hValueOff = -1;
112
+ data->hValueLen = 0;
113
+ }
114
+
115
+ return NULL;
116
+ }
117
+
118
+ FUNCTION(Jill_writer)
119
+ {
120
+ GET_INTERNAL(client_data*, data, THIS);
121
+ if (!data)
122
+ THROW("Problem writing");
123
+
124
+ // FIXME: this is failing on legit objects for some reason
125
+ //if (ARGC < 1 || !IS_OBJECT(ARGV(0)))
126
+ // THROW("First argument to writer must be an object which has a toByteString method");
127
+
128
+ //CALL(JSValuePrint, ARGV(0));
129
+ //HANDLE_EXCEPTION(true, true);
130
+
131
+ NWObject chunk = TO_OBJECT(ARGV(0));
132
+ HANDLE_EXCEPTION(true, true);
133
+
134
+ NWObject toByteString = GET_OBJECT(chunk, "toByteString");
135
+ HANDLE_EXCEPTION(true, true);
136
+
137
+ if (!IS_FUNCTION(toByteString))
138
+ THROW("First argument to writer must be an object which has a toByteString method");
139
+
140
+ NWValue byteStringValue = CALL_AS_FUNCTION(toByteString, chunk, 0, NULL);
141
+ HANDLE_EXCEPTION(true, true);
142
+
143
+ if (!IS_OBJECT(byteStringValue))
144
+ THROW("toByteString did not return a ByteString object");
145
+
146
+ NWObject byteString = TO_OBJECT(byteStringValue);
147
+ HANDLE_EXCEPTION(true, true);
148
+
149
+ NWObject _bytes = GET_OBJECT(byteString, "_bytes");
150
+ HANDLE_EXCEPTION(true, true);
151
+
152
+ GET_INTERNAL(BytesPrivate*, bytesData, _bytes);
153
+
154
+ if (send(data->fd, bytesData->buffer, bytesData->length, 0) < 0)
155
+ THROW("Client closed connection");
156
+
157
+ return JS_undefined;
158
+ }
159
+ END
160
+
161
+ NWValue handlerWrapper(
162
+ JSContextRef _context, JSValueRef *_exception,
163
+ http_parser *parser
164
+ ) {
165
+ client_data *data = (client_data *)parser->data;
166
+ NWObject env = data->env;
167
+
168
+ // add the final header
169
+ CALL(processHeader, data);
170
+
171
+ // SCRIPT_NAME
172
+ SET_VALUE(env, "SCRIPT_NAME", JS_str_utf8("", 0));
173
+ HANDLE_EXCEPTION(true, true);
174
+
175
+ // PATH_INFO
176
+ if (data->pathOff >= 0) {
177
+ char *path = data->base + data->pathOff;
178
+ path[data->pathLen] = '\0';
179
+ SET_VALUE(env, "PATH_INFO", JS_str_utf8(path, data->pathLen));
180
+ } else {
181
+ SET_VALUE(env, "PATH_INFO", JS_str_utf8("", 0));
182
+ }
183
+ HANDLE_EXCEPTION(true, true);
184
+
185
+ // QUERY_STRING
186
+ if (data->queryOff >= 0) {
187
+ char *query = data->base + data->queryOff;
188
+ query[data->queryLen] = '\0';
189
+ SET_VALUE(env, "QUERY_STRING", JS_str_utf8(query, data->queryLen));
190
+ } else {
191
+ SET_VALUE(env, "QUERY_STRING", JS_str_utf8("", 0));
192
+ }
193
+ HANDLE_EXCEPTION(true, true);
194
+
195
+ // REQUEST_METHOD
196
+ char *methodName = "GET";
197
+ switch (parser->method) {
198
+ case HTTP_COPY : methodName = "COPY"; break;
199
+ case HTTP_DELETE : methodName = "DELETE"; break;
200
+ case HTTP_HEAD : methodName = "HEAD"; break;
201
+ case HTTP_LOCK : methodName = "LOCK"; break;
202
+ case HTTP_MKCOL : methodName = "MKCOL"; break;
203
+ case HTTP_MOVE : methodName = "MOVE"; break;
204
+ case HTTP_OPTIONS : methodName = "OPTIONS"; break;
205
+ case HTTP_POST : methodName = "POST"; break;
206
+ case HTTP_PROPFIND : methodName = "PROPFIND"; break;
207
+ case HTTP_PROPPATCH : methodName = "PROPPATCH"; break;
208
+ case HTTP_PUT : methodName = "PUT"; break;
209
+ case HTTP_TRACE : methodName = "TRACE"; break;
210
+ case HTTP_UNLOCK : methodName = "UNLOCK"; break;
211
+ case HTTP_GET : methodName = "GET"; break;
212
+ }
213
+ SET_VALUE(env, "REQUEST_METHOD", JS_str_utf8(methodName, strlen(methodName)));
214
+ HANDLE_EXCEPTION(true, true);
215
+
216
+ // SERVER_PROTOCOL
217
+ char *versionName = "HTTP/1.1";
218
+ switch (parser->version) {
219
+ case HTTP_VERSION_09 : versionName = "HTTP/0.9"; break;
220
+ case HTTP_VERSION_10 : versionName = "HTTP/1.0"; break;
221
+ case HTTP_VERSION_11 : versionName = "HTTP/1.1"; break;
222
+ }
223
+ SET_VALUE(env, "SERVER_PROTOCOL", JS_str_utf8(versionName, strlen(versionName)));
224
+ HANDLE_EXCEPTION(true, true);
225
+
226
+ // SERVER_NAME
227
+ SET_VALUE(env, "SERVER_NAME", data->SERVER_NAME ? data->SERVER_NAME : JS_str_utf8("", 0));
228
+ HANDLE_EXCEPTION(true, true);
229
+
230
+ // SERVER_PORT
231
+ SET_VALUE(env, "SERVER_PORT", data->SERVER_PORT ? data->SERVER_PORT : JS_str_utf8("", 0));
232
+ HANDLE_EXCEPTION(true, true);
233
+
234
+ // REMOTE_ADDR
235
+ struct sockaddr_in client_addr;
236
+ socklen_t client_addr_len = sizeof(client_addr);
237
+ if (getpeername(data->fd, (struct sockaddr*)&client_addr, &client_addr_len) == 0) {
238
+ char *ip_buffer = inet_ntoa(client_addr.sin_addr);
239
+ if (ip_buffer) {
240
+ SET_VALUE(env, "REMOTE_USER", JS_str_utf8(ip_buffer, strlen(ip_buffer)));
241
+ HANDLE_EXCEPTION(true, true);
242
+ } else {
243
+ DEBUG("inet_ntoa failed\n");
244
+ }
245
+ } else {
246
+ DEBUG("getpeername failed\n");
247
+ }
248
+
249
+ // jsgi.version
250
+ ARGS_ARRAY(argvVersion, JS_int(0), JS_int(2));
251
+ SET_VALUE(env, "jsgi.version", JS_array(2, argvVersion));
252
+ HANDLE_EXCEPTION(true, true);
253
+
254
+ // jsgi.erros
255
+ SET_VALUE(env, "jsgi.errors", GET_OBJECT(System, "stderr"));
256
+ HANDLE_EXCEPTION(true, true);
257
+
258
+ char *body;
259
+ ssize_t bodyLen = 0;
260
+ // jsgi.input
261
+ if (data->bodyOff < 0) {
262
+ body = "";
263
+ bodyLen = 0;
264
+ } else {
265
+ body = data->base + data->bodyOff;
266
+ bodyLen = data->bodyLen;
267
+ }
268
+
269
+ // TODO: modify parsing to use a separate buffer directly to prevent copying
270
+ char *bodyBuffer = (char *)malloc(bodyLen * sizeof(char));
271
+ memcpy(bodyBuffer, body, bodyLen);
272
+
273
+ NWObject bytes = CALL(Bytes_new, bodyBuffer, bodyLen);
274
+ HANDLE_EXCEPTION(true, true);
275
+
276
+ // new ByteString(bytes, 0, 0)
277
+ ARGS_ARRAY(byteStringArgs, bytes, JS_int(0), JS_int(bodyLen));
278
+ NWObject byteString = CALL_AS_CONSTRUCTOR(ByteString, 3, byteStringArgs);
279
+
280
+ // new ByteIO(byteString)
281
+ ARGS_ARRAY(byteIoArgs, byteString);
282
+ NWObject input = CALL_AS_CONSTRUCTOR(ByteIO, 1, byteIoArgs);
283
+ HANDLE_EXCEPTION(true, true);
284
+
285
+ SET_VALUE(env, "jsgi.input", input);
286
+ HANDLE_EXCEPTION(true, true);
287
+
288
+ // jsgi.multithread
289
+ SET_VALUE(env, "jsgi.multithread", JS_bool(0));
290
+ HANDLE_EXCEPTION(true, true);
291
+
292
+ // jsgi.multiprocess
293
+ SET_VALUE(env, "jsgi.multiprocess", JS_bool(1));
294
+ HANDLE_EXCEPTION(true, true);
295
+
296
+ // jsgi.run_once
297
+ SET_VALUE(env, "jsgi.run_once", JS_bool(0));
298
+ HANDLE_EXCEPTION(true, true);
299
+
300
+ // jsgi.url_scheme
301
+ SET_VALUE(env, "jsgi.url_scheme", JS_str_utf8("http", strlen("http")));
302
+ HANDLE_EXCEPTION(true, true);
303
+
304
+ // Invoke the application
305
+ ARGS_ARRAY(appArgs, env);
306
+ NWValue res = CALL_AS_FUNCTION(data->app, NULL, 1, appArgs);
307
+ //HANDLE_EXCEPTION(true, true);
308
+
309
+ char buffer[1024];
310
+
311
+ if (*_exception) {
312
+ JSValuePrint(_context, NULL, *_exception);
313
+ snprintf(buffer, sizeof(buffer), "%s %s\r\n", "HTTP/1.1", "500 Internal Server Error");
314
+ if (send(data->fd, buffer, strlen(buffer), 0) < 0)
315
+ THROW("Client closed connection");
316
+ snprintf(buffer, sizeof(buffer), "%s: %s\r\n", "Content-Type", "text/plain");
317
+ if (send(data->fd, buffer, strlen(buffer), 0) < 0)
318
+ THROW("Client closed connection");
319
+ snprintf(buffer, sizeof(buffer), "%s: %s\r\n", "Connection", "close");
320
+ if (send(data->fd, buffer, strlen(buffer), 0) < 0)
321
+ THROW("Client closed connection");
322
+ snprintf(buffer, sizeof(buffer), "\r\n");
323
+ if (send(data->fd, buffer, strlen(buffer), 0) < 0)
324
+ THROW("Client closed connection");
325
+ snprintf(buffer, sizeof(buffer), "%s", "500 Internal Server Error");
326
+ if (send(data->fd, buffer, strlen(buffer), 0) < 0)
327
+ THROW("Client closed connection");
328
+ return NULL;
329
+ }
330
+
331
+ NWObject result = TO_OBJECT(res);
332
+ HANDLE_EXCEPTION(true, true);
333
+
334
+ // STATUS:
335
+ int status = GET_INT(result, "status");
336
+ HANDLE_EXCEPTION(true, true);
337
+
338
+ char *reason = "OK"; // FIXME
339
+ snprintf(buffer, sizeof(buffer), "%s %d %s\r\n", "HTTP/1.1", status, reason);
340
+ if (send(data->fd, buffer, strlen(buffer), 0) < 0)
341
+ THROW("Client closed connection");
342
+
343
+ // HEADERS:
344
+ NWObject headers = GET_OBJECT(result, "headers");
345
+ JSPropertyNameArrayRef headerNames = JSObjectCopyPropertyNames(_context, headers);
346
+ size_t headerCount = JSPropertyNameArrayGetCount(headerNames);
347
+
348
+ for (size_t i = 0; i < headerCount; i++) {
349
+ JSStringRef headerName = JSPropertyNameArrayGetNameAtIndex(headerNames, i);
350
+
351
+ size_t sz = JSStringGetMaximumUTF8CStringSize(headerName);
352
+ char name[sz];
353
+ JSStringGetUTF8CString(headerName, name, sz);
354
+
355
+ JSStringRef headerValue = JSValueToStringCopy(_context, GET_VALUE(headers, name), _exception);
356
+
357
+ sz = JSStringGetMaximumUTF8CStringSize(headerValue);
358
+ char value[sz];
359
+ JSStringGetUTF8CString(headerValue, value, sz);
360
+
361
+ char *start = value, *end = value;
362
+ for (;;) {
363
+ while (*end != '\n' && *end != '\0') end++;
364
+ char orig = *end;
365
+
366
+ *end = '\0';
367
+ snprintf(buffer, sizeof(buffer), "%s: %s\r\n", name, start);
368
+ if (send(data->fd, buffer, strlen(buffer), 0) < 0)
369
+ THROW("Client closed connection");
370
+ *end = orig;
371
+
372
+ if (orig == '\0') break;
373
+ end = start = end + 1;
374
+ }
375
+ }
376
+
377
+ snprintf(buffer, sizeof(buffer), "\r\n");
378
+ if (send(data->fd, buffer, strlen(buffer), 0) < 0)
379
+ THROW("Client closed connection");
380
+
381
+ // BODY:
382
+ NWObject bodyObject = GET_OBJECT(result, "body");
383
+ HANDLE_EXCEPTION(true, true);
384
+ NWObject forEach = GET_OBJECT(bodyObject, "forEach");
385
+ HANDLE_EXCEPTION(true, true);
386
+
387
+ NWObject writer = JS_fn(Jill_writer);
388
+
389
+ NWObject binder = GET_OBJECT(writer, "bind");
390
+ HANDLE_EXCEPTION(true, true);
391
+
392
+ NWObject that = JSObjectMake(_context, Custom_class(_context), data);
393
+
394
+ ARGS_ARRAY(bindArgs, that);
395
+ NWValue boundWriter = CALL_AS_FUNCTION(binder, writer, 1, bindArgs);
396
+ HANDLE_EXCEPTION(true, true);
397
+
398
+ ARGS_ARRAY(forEachArgs, boundWriter);
399
+ CALL_AS_FUNCTION(forEach, bodyObject, 1, forEachArgs);
400
+ HANDLE_EXCEPTION(true, true);
401
+
402
+ return 0;
403
+ }
404
+
405
+ void dispatch(http_parser *parser) {
406
+ client_data *data = (client_data *)parser->data;
407
+ JSContextRef _context = data->_context;
408
+ JSValueRef *_exception = data->_exception;
409
+
410
+ if (data->complete)
411
+ return;
412
+
413
+ data->complete = 1;
414
+
415
+ DEBUG("Dispatching...\n");
416
+
417
+ *_exception = NULL;
418
+ NWValue result = CALL(handlerWrapper, parser);
419
+
420
+ if (*_exception) {
421
+ JS_Print(*_exception);
422
+ }
423
+ }
424
+
425
+ int on_message_begin(http_parser *parser) {
426
+ client_data *data = (client_data *)parser->data;
427
+ JSContextRef _context = data->_context;
428
+ //JSValueRef *_exception = data->_exception;
429
+
430
+ data->env = JSObjectMake(_context, NULL, NULL);
431
+
432
+ return 0;
433
+ }
434
+
435
+ int on_path(http_parser *parser, const char *at, size_t length) {
436
+ client_data *data = (client_data *)parser->data;
437
+ // JSContextRef _context = data->_context;
438
+ // JSValueRef *_exception = data->_exception;
439
+
440
+ if (data->pathOff < 0) {
441
+ data->pathOff = at - data->base;
442
+ data->pathLen = length;
443
+ }
444
+ else {
445
+ data->pathLen += length;
446
+ }
447
+
448
+ return 0;
449
+ }
450
+
451
+ int on_query_string(http_parser *parser, const char *at, size_t length) {
452
+ client_data *data = (client_data *)parser->data;
453
+ // JSContextRef _context = data->_context;
454
+ // JSValueRef *_exception = data->_exception;
455
+
456
+ if (data->queryOff < 0) {
457
+ data->queryOff = at - data->base;
458
+ data->queryLen = length;
459
+ }
460
+ else {
461
+ data->queryLen += length;
462
+ }
463
+
464
+ return 0;
465
+ }
466
+
467
+ int on_header_field(http_parser *parser, const char *at, size_t length) {
468
+ client_data *data = (client_data *)parser->data;
469
+ JSContextRef _context = data->_context;
470
+ JSValueRef *_exception = data->_exception;
471
+
472
+ // add header, if there's one pending
473
+ CALL(processHeader, data);
474
+
475
+ if (data->hNameOff < 0) {
476
+ data->hNameOff = at - data->base;
477
+ data->hNameLen = length;
478
+ }
479
+ else {
480
+ data->hNameLen += length;
481
+ }
482
+
483
+ return 0;
484
+ }
485
+
486
+ int on_header_value(http_parser *parser, const char *at, size_t length) {
487
+ client_data *data = (client_data *)parser->data;
488
+ //JSContextRef _context = data->_context;
489
+ //JSValueRef *_exception = data->_exception;
490
+
491
+ if (data->hValueOff < 0) {
492
+ data->hValueOff = at - data->base;
493
+ data->hValueLen = length;
494
+ }
495
+ else {
496
+ data->hValueLen += length;
497
+ }
498
+
499
+ return 0;
500
+ }
501
+
502
+ // FIXME: stream body (buffer first "on_body" chunk, then use raw socket?)
503
+ int on_body(http_parser *parser, const char *at, size_t length) {
504
+ client_data *data = (client_data *)parser->data;
505
+ //JSContextRef _context = data->_context;
506
+ //JSValueRef *_exception = data->_exception;
507
+
508
+
509
+ if (data->bodyOff < 0) {
510
+ data->bodyOff = at - data->base;
511
+ data->bodyLen = length;
512
+ }
513
+ else {
514
+ data->bodyLen += length;
515
+ }
516
+
517
+ // printf("on_body: %d (%d)\n", length, data->bodyLen);
518
+
519
+ return 0;
520
+ }
521
+
522
+ int on_headers_complete(http_parser *parser) {
523
+ //client_data *data = (client_data *)parser->data;
524
+ //JSContextRef _context = data->_context;
525
+ //JSValueRef *_exception = data->_exception;
526
+
527
+ //printf("on_headers_complete\n");
528
+
529
+ // headers done but no content-length, assume no body
530
+ // TODO: is this the desired behavior?
531
+ if (parser->content_length < 0)
532
+ dispatch(parser);
533
+
534
+ return 0;
535
+ }
536
+
537
+ int on_message_complete(http_parser *parser) {
538
+ //client_data *data = (client_data *)parser->data;
539
+ //JSContextRef _context = data->_context;
540
+ //JSValueRef *_exception = data->_exception;
541
+
542
+ //printf("on_message_complete\n");
543
+
544
+ dispatch(parser);
545
+
546
+ return 0;
547
+ }
548
+
549
+ FUNCTION(Jill_run, ARG_FN(app))
550
+ {
551
+ int port = 8080;
552
+ int backlog = 100;
553
+ in_addr_t host = INADDR_ANY;
554
+
555
+ if (ARGC > 1 && IS_OBJECT(ARGV(1))) {
556
+ NWObject options = TO_OBJECT(ARGV(1));
557
+ if (HAS_PROPERTY(options, "port")) {
558
+ port = GET_INT(options, "port");
559
+ HANDLE_EXCEPTION(true, true);
560
+ }
561
+ if (HAS_PROPERTY(options, "host")) {
562
+ NWValue hostValue = GET_VALUE(options, "host");
563
+ HANDLE_EXCEPTION(true, true);
564
+ GET_UTF8(hostStr, hostValue);
565
+ host = inet_addr(hostStr);
566
+ }
567
+ }
568
+ fprintf(stdout, "Jack is starting up using Jill on port %d\n", port);
569
+ fflush(stdout);
570
+
571
+ int server_socket, client_socket;
572
+ struct sockaddr_in server_address = {0}, client_address = {0};
573
+
574
+ server_socket = socket(AF_INET, SOCK_STREAM, 0);
575
+ if (server_socket < 0)
576
+ THROW("socket error: %s", strerror(errno));
577
+
578
+ int optval = 1;
579
+ // free up the bound port immediately
580
+ setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
581
+
582
+ server_address.sin_family = AF_INET;
583
+ server_address.sin_addr.s_addr = host;
584
+ server_address.sin_port = htons(port);
585
+
586
+ if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) < 0)
587
+ THROW("bind error: %s", strerror(errno));
588
+
589
+ if (listen(server_socket, backlog) < 0)
590
+ THROW("listen error: %s", strerror(errno));
591
+
592
+ size_t bufferSize = 1024;
593
+ size_t bufferPosition = 0;
594
+ char *buffer = (char *)malloc(bufferSize);
595
+
596
+ while (1) {
597
+ socklen_t client_addrlen = sizeof(struct sockaddr_in);
598
+ client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_addrlen);
599
+ if (client_socket < 0) {
600
+ // TODO: should we really throw an exception here, or do something else?
601
+ close(server_socket);
602
+ THROW("accept error: %s", strerror(errno));
603
+ }
604
+
605
+ int optval = 1;
606
+ // don't signal SIGPIPE if a socket is closed early
607
+ setsockopt(client_socket, SOL_SOCKET, SO_NOSIGPIPE, (void *)&optval, sizeof(optval));
608
+
609
+ client_data data;
610
+ memset(&data, 0, sizeof(data));
611
+ data.base = buffer;
612
+ data.pathOff = -1;
613
+ data.queryOff = -1;
614
+ data.hNameOff = -1;
615
+ data.hValueOff = -1;
616
+ data.bodyOff = -1;
617
+
618
+ data.fd = client_socket;
619
+ data._context = _context;
620
+ JSValueRef exception = NULL;
621
+ data._exception = &exception;
622
+ data.app = app;
623
+
624
+ http_parser parser;
625
+ http_parser_init(&parser, HTTP_REQUEST);
626
+
627
+ parser.data = &data;
628
+
629
+ parser.on_message_begin = on_message_begin;
630
+ parser.on_path = on_path;
631
+ parser.on_query_string = on_query_string;
632
+ parser.on_header_field = on_header_field;
633
+ parser.on_header_value = on_header_value;
634
+ parser.on_headers_complete = on_headers_complete;
635
+ parser.on_body = on_body;
636
+ parser.on_message_complete = on_message_complete;
637
+
638
+ bufferPosition = 0;
639
+
640
+ while (1) {
641
+
642
+ if (bufferPosition >= bufferSize) {
643
+ if (bufferPosition > bufferSize) {
644
+ fprintf(stderr, "bufferPosition=%d bufferSize=%d WHAT?!\n", bufferPosition, bufferSize);
645
+ }
646
+
647
+ bufferSize *= 2;
648
+
649
+ fprintf(stderr, "reallocing bufferSize=%d\n", bufferSize);
650
+
651
+ data.base = buffer = (char *)realloc(buffer, bufferSize);
652
+ if (!data.base) {
653
+ THROW("OOM!");
654
+ close(client_socket);
655
+ }
656
+ }
657
+
658
+ // FIXME: timeout
659
+ ssize_t recved = recv(client_socket, data.base + bufferPosition, bufferSize - bufferPosition, 0);
660
+ if (recved < 0) {
661
+ printf("recv error: %s\n", strerror(errno));
662
+ break;
663
+ }
664
+
665
+ http_parser_execute(&parser, data.base + bufferPosition, recved);
666
+
667
+ if (http_parser_has_error(&parser)) {
668
+ printf("parse error\n");
669
+ if (*data._exception) {
670
+ JS_Print(*data._exception);
671
+ }
672
+ break;
673
+ }
674
+
675
+ if (data.complete) {
676
+ break;
677
+ }
678
+
679
+ if (recved == 0) {
680
+ break;
681
+ }
682
+
683
+ bufferPosition += recved;
684
+ }
685
+
686
+ close(client_socket);
687
+ }
688
+
689
+ return JS_undefined;
690
+ }
691
+ END
692
+
693
+ NARWHAL_MODULE(http_server_engine)
694
+ {
695
+ EXPORTS("run", JS_fn(Jill_run));
696
+
697
+ NWObject io = require("io");
698
+ HANDLE_EXCEPTION(true, true);
699
+
700
+ ByteIO = GET_OBJECT(io, "ByteIO");
701
+ HANDLE_EXCEPTION(true, true);
702
+
703
+ NWObject binary = require("binary");
704
+ HANDLE_EXCEPTION(true, true);
705
+
706
+ ByteString = GET_OBJECT(binary, "ByteString");
707
+ HANDLE_EXCEPTION(true, true);
708
+
709
+ }
710
+ END_NARWHAL_MODULE