sproutit-narwhal 0.1.106
Sign up to get free protection for your applications and to get access to all the features.
- data/DISTRIBUTION.yml +15 -0
- data/README.md +86 -0
- data/Rakefile +349 -0
- data/VERSION.yml +7 -0
- data/bin/activate +50 -0
- data/bin/activate.bash +50 -0
- data/bin/activate.cmd +3 -0
- data/bin/js +67 -0
- data/bin/json +2 -0
- data/bin/narwhal +67 -0
- data/bin/narwhal.cmd +29 -0
- data/bin/sea +45 -0
- data/bin/sea.cmd +25 -0
- data/bin/tusk +2 -0
- data/bin/tusk.cmd +5 -0
- data/catalog.json +902 -0
- data/docs/available-packages.md +32 -0
- data/docs/browser-api-plan.md +290 -0
- data/docs/browser-api.md +153 -0
- data/docs/download.md +25 -0
- data/docs/engines.md +32 -0
- data/docs/json-tool.md +121 -0
- data/docs/lib/binary.wiki +242 -0
- data/docs/lib/file.wiki +325 -0
- data/docs/lib/os/popen.md +70 -0
- data/docs/modules.md +38 -0
- data/docs/narwhal.md +487 -0
- data/docs/packages-howto.md +32 -0
- data/docs/packages.md +30 -0
- data/docs/posts/2009-07-29-hello-0.1.md +19 -0
- data/docs/quick-start.md +69 -0
- data/docs/sea.md +49 -0
- data/engines/browser/lib/binary.js +2 -0
- data/engines/browser/lib/reactor.js +21 -0
- data/engines/browser/lib/system.js +3 -0
- data/engines/default/lib/array.js +164 -0
- data/engines/default/lib/binary-engine.js +53 -0
- data/engines/default/lib/binary.js +755 -0
- data/engines/default/lib/date.js +8 -0
- data/engines/default/lib/file-engine.js +119 -0
- data/engines/default/lib/function.js +119 -0
- data/engines/default/lib/global.js +11 -0
- data/engines/default/lib/io-engine.js +26 -0
- data/engines/default/lib/json.js +488 -0
- data/engines/default/lib/object.js +69 -0
- data/engines/default/lib/os-engine.js +3 -0
- data/engines/default/lib/reactor.js +12 -0
- data/engines/default/lib/string.js +84 -0
- data/engines/default/lib/system.js +20 -0
- data/engines/default/lib/worker.js +133 -0
- data/engines/jsc/README.md +18 -0
- data/engines/jsc/bootstrap.js +53 -0
- data/engines/jsc/deps/http-parser/LICENSE +77 -0
- data/engines/jsc/deps/http-parser/README.md +145 -0
- data/engines/jsc/deps/http-parser/http_parser.c +6087 -0
- data/engines/jsc/deps/http-parser/http_parser.h +141 -0
- data/engines/jsc/deps/http-parser/http_parser.rl +500 -0
- data/engines/jsc/deps/http-parser/test.c +858 -0
- data/engines/jsc/include/binary-engine.h +11 -0
- data/engines/jsc/include/io-engine.h +23 -0
- data/engines/jsc/include/narwhal.h +427 -0
- data/engines/jsc/lib/file-engine.js +31 -0
- data/engines/jsc/lib/http.js +1 -0
- data/engines/jsc/lib/io-engine.js +202 -0
- data/engines/jsc/lib/os-engine.js +25 -0
- data/engines/jsc/lib/system.js +18 -0
- data/engines/jsc/lib/zip.js +1 -0
- data/engines/jsc/narwhal-jsc.c +273 -0
- data/engines/jsc/narwhal.c +29 -0
- data/engines/jsc/package.json +8 -0
- data/engines/jsc/src/binary-engine.cc +290 -0
- data/engines/jsc/src/file-engine.cc +405 -0
- data/engines/jsc/src/io-engine.cc +423 -0
- data/engines/jsc/src/jack/handler/jill.cc +710 -0
- data/engines/jsc/src/os-engine.cc +210 -0
- data/engines/rhino/bin/narwhal-rhino +68 -0
- data/engines/rhino/bin/narwhal-rhino.cmd +34 -0
- data/engines/rhino/bootstrap.js +119 -0
- data/engines/rhino/jars/jline.jar +0 -0
- data/engines/rhino/jars/jna.jar +0 -0
- data/engines/rhino/jars/js.jar +0 -0
- data/engines/rhino/lib/binary-engine.js +83 -0
- data/engines/rhino/lib/concurrency.js +6 -0
- data/engines/rhino/lib/event-queue.js +18 -0
- data/engines/rhino/lib/file-engine.js +216 -0
- data/engines/rhino/lib/http-client-engine.js +90 -0
- data/engines/rhino/lib/http-engine.js +10 -0
- data/engines/rhino/lib/io-engine.js +347 -0
- data/engines/rhino/lib/md5-engine.js +40 -0
- data/engines/rhino/lib/os-engine.js +150 -0
- data/engines/rhino/lib/packages-engine.js +71 -0
- data/engines/rhino/lib/sandbox-engine.js +70 -0
- data/engines/rhino/lib/system.js +38 -0
- data/engines/rhino/lib/worker-engine.js +23 -0
- data/engines/rhino/lib/zip.js +78 -0
- data/engines/rhino/package.json +4 -0
- data/engines/secure/lib/file.js +6 -0
- data/engines/secure/lib/system.js +6 -0
- data/engines/template/bin/narwhal-engine-name +32 -0
- data/engines/template/bootstrap.js +40 -0
- data/engines/template/lib/file-engine.js +118 -0
- data/engines/template/lib/system.js +17 -0
- data/examples/browser-deployment-jackconfig.js +35 -0
- data/examples/fibonacci-worker.js +35 -0
- data/examples/fibonacci.js +19 -0
- data/examples/hello +2 -0
- data/examples/narwhal +3 -0
- data/examples/not-quite-a-quine.js +1 -0
- data/extconf.rb +44 -0
- data/gem_bin/narwhal +5 -0
- data/gem_bin/sea +4 -0
- data/gem_bin/tusk +4 -0
- data/lib/args.js +849 -0
- data/lib/base16.js +16 -0
- data/lib/base64.js +120 -0
- data/lib/codec/base64.js +8 -0
- data/lib/crc32.js +60 -0
- data/lib/file-bootstrap.js +187 -0
- data/lib/file.js +659 -0
- data/lib/hash.js +28 -0
- data/lib/hashp.js +65 -0
- data/lib/html.js +16 -0
- data/lib/http-client.js +134 -0
- data/lib/http.js +17 -0
- data/lib/io.js +98 -0
- data/lib/jsmin.js +315 -0
- data/lib/jsonpath.js +89 -0
- data/lib/logger.js +55 -0
- data/lib/md4.js +146 -0
- data/lib/md5.js +164 -0
- data/lib/mime.js +166 -0
- data/lib/narwhal.js +102 -0
- data/lib/narwhal/client.js +261 -0
- data/lib/narwhal/compile.js +99 -0
- data/lib/narwhal/env.js +140 -0
- data/lib/narwhal/inline.js +106 -0
- data/lib/narwhal/json.js +324 -0
- data/lib/narwhal/json.md +178 -0
- data/lib/narwhal/repl.js +96 -0
- data/lib/narwhal/server-test.js +6 -0
- data/lib/narwhal/server.js +270 -0
- data/lib/narwhal/tusk.js +170 -0
- data/lib/narwhal/tusk/bin.js +13 -0
- data/lib/narwhal/tusk/bundle.js +0 -0
- data/lib/narwhal/tusk/catalog.js +22 -0
- data/lib/narwhal/tusk/clone.js +66 -0
- data/lib/narwhal/tusk/consolidate.js +25 -0
- data/lib/narwhal/tusk/create-catalog.js +80 -0
- data/lib/narwhal/tusk/engine.js +42 -0
- data/lib/narwhal/tusk/freeze.js +0 -0
- data/lib/narwhal/tusk/init.js +56 -0
- data/lib/narwhal/tusk/install.js +288 -0
- data/lib/narwhal/tusk/list.js +20 -0
- data/lib/narwhal/tusk/orphans.js +0 -0
- data/lib/narwhal/tusk/reheat.js +15 -0
- data/lib/narwhal/tusk/remove.js +15 -0
- data/lib/narwhal/tusk/search.js +145 -0
- data/lib/narwhal/tusk/update.js +21 -0
- data/lib/narwhal/tusk/upgrade.js +0 -0
- data/lib/os.js +33 -0
- data/lib/packages.js +423 -0
- data/lib/printf.js +169 -0
- data/lib/promise.js +352 -0
- data/lib/querystring.js +176 -0
- data/lib/ref-send.js +257 -0
- data/lib/regexp.js +12 -0
- data/lib/sandbox.js +422 -0
- data/lib/sha.js +112 -0
- data/lib/sha256.js +102 -0
- data/lib/struct.js +228 -0
- data/lib/term.js +179 -0
- data/lib/test/assert.js +95 -0
- data/lib/test/equiv.js +188 -0
- data/lib/test/jsdump.js +165 -0
- data/lib/test/runner.js +129 -0
- data/lib/unload.js +13 -0
- data/lib/uri.js +378 -0
- data/lib/url.js +5 -0
- data/lib/utf8.js +64 -0
- data/lib/util.js +985 -0
- data/lib/uuid.js +89 -0
- data/lib/xregexp.js +521 -0
- data/local.json.template +1 -0
- data/narwhal.gemspec +105 -0
- data/narwhal.js +213 -0
- data/package.json +26 -0
- data/packages/readline/engines/default/lib/readline.js +4 -0
- data/packages/readline/engines/rhino/lib/readline.js +6 -0
- data/packages/readline/package.json +5 -0
- data/sources.json +207 -0
- data/tests/all-tests.js +17 -0
- data/tests/args.js +31 -0
- data/tests/args/domain.js +215 -0
- data/tests/args/options.js +36 -0
- data/tests/args/shifting.js +92 -0
- data/tests/args/validation.js +31 -0
- data/tests/base64.js +23 -0
- data/tests/commonjs.js +3 -0
- data/tests/commonjs/all-tests.js +12 -0
- data/tests/commonjs/bytearray-encodings-tests.js +69 -0
- data/tests/commonjs/bytearray-tests.js +465 -0
- data/tests/commonjs/bytestring-encodings-tests.js +89 -0
- data/tests/commonjs/bytestring-tests.js +263 -0
- data/tests/commonjs/es5/all-tests.js +3 -0
- data/tests/commonjs/es5/bind.js +29 -0
- data/tests/commonjs/file-tests.js +315 -0
- data/tests/commonjs/file/dirname.js +31 -0
- data/tests/commonjs/file/extension.js +45 -0
- data/tests/commonjs/file/is-absolute.js +11 -0
- data/tests/commonjs/file/iterator.js +101 -0
- data/tests/commonjs/file/normal.js +27 -0
- data/tests/commonjs/file/path.js +17 -0
- data/tests/commonjs/file/relative.js +42 -0
- data/tests/commonjs/file/resolve.js +44 -0
- data/tests/commonjs/module-tests.js +9 -0
- data/tests/commonjs/modules/absolute/b.js +1 -0
- data/tests/commonjs/modules/absolute/program.js +5 -0
- data/tests/commonjs/modules/absolute/submodule/a.js +3 -0
- data/tests/commonjs/modules/absolute/test.js +9 -0
- data/tests/commonjs/modules/all-tests.js +47 -0
- data/tests/commonjs/modules/config.js +11 -0
- data/tests/commonjs/modules/cyclic/a.js +4 -0
- data/tests/commonjs/modules/cyclic/b.js +4 -0
- data/tests/commonjs/modules/cyclic/program.js +10 -0
- data/tests/commonjs/modules/cyclic/test.js +9 -0
- data/tests/commonjs/modules/determinism/program.js +3 -0
- data/tests/commonjs/modules/determinism/submodule/a.js +8 -0
- data/tests/commonjs/modules/determinism/submodule/b.js +2 -0
- data/tests/commonjs/modules/determinism/test.js +9 -0
- data/tests/commonjs/modules/exactExports/a.js +3 -0
- data/tests/commonjs/modules/exactExports/program.js +4 -0
- data/tests/commonjs/modules/exactExports/test.js +9 -0
- data/tests/commonjs/modules/hasOwnProperty/hasOwnProperty.js +0 -0
- data/tests/commonjs/modules/hasOwnProperty/program.js +3 -0
- data/tests/commonjs/modules/hasOwnProperty/test.js +9 -0
- data/tests/commonjs/modules/hasOwnProperty/toString.js +0 -0
- data/tests/commonjs/modules/method/a.js +12 -0
- data/tests/commonjs/modules/method/program.js +8 -0
- data/tests/commonjs/modules/method/test.js +9 -0
- data/tests/commonjs/modules/missing/program.js +8 -0
- data/tests/commonjs/modules/missing/test.js +9 -0
- data/tests/commonjs/modules/monkeys/a.js +1 -0
- data/tests/commonjs/modules/monkeys/program.js +4 -0
- data/tests/commonjs/modules/monkeys/test.js +9 -0
- data/tests/commonjs/modules/nested/a/b/c/d.js +3 -0
- data/tests/commonjs/modules/nested/program.js +3 -0
- data/tests/commonjs/modules/nested/test.js +9 -0
- data/tests/commonjs/modules/relative/program.js +5 -0
- data/tests/commonjs/modules/relative/submodule/a.js +1 -0
- data/tests/commonjs/modules/relative/submodule/b.js +2 -0
- data/tests/commonjs/modules/relative/test.js +9 -0
- data/tests/commonjs/modules/transitive/a.js +1 -0
- data/tests/commonjs/modules/transitive/b.js +1 -0
- data/tests/commonjs/modules/transitive/c.js +3 -0
- data/tests/commonjs/modules/transitive/program.js +3 -0
- data/tests/commonjs/modules/transitive/test.js +9 -0
- data/tests/file/all-tests.js +61 -0
- data/tests/file/fnmatch.js +102 -0
- data/tests/file/glob.js +466 -0
- data/tests/file/match.js +102 -0
- data/tests/global.js +6 -0
- data/tests/global/array.js +19 -0
- data/tests/hashes.js +94 -0
- data/tests/html.js +13 -0
- data/tests/io/stringio.js +21 -0
- data/tests/os/all-tests.js +4 -0
- data/tests/os/popen.js +41 -0
- data/tests/os/system.js +22 -0
- data/tests/printf.js +123 -0
- data/tests/query-string.js +87 -0
- data/tests/sandbox/byte-io.js +20 -0
- data/tests/sandbox/fileName.js +3 -0
- data/tests/sandbox/foo.js +0 -0
- data/tests/sandbox/reload.js +79 -0
- data/tests/string.js +35 -0
- data/tests/uri.js +41 -0
- data/tests/util/all-tests.js +79 -0
- data/tests/util/array.js +207 -0
- data/tests/util/array/is-arguments.js +29 -0
- data/tests/util/array/is-array-like.js +29 -0
- data/tests/util/case.js +9 -0
- data/tests/util/collection.js +104 -0
- data/tests/util/eq.js +57 -0
- data/tests/util/expand.js +45 -0
- data/tests/util/object.js +125 -0
- data/tests/util/operator.js +25 -0
- data/tests/util/range.js +19 -0
- data/tests/util/repr.js +26 -0
- data/tests/util/string.js +34 -0
- data/tests/util/unique.js +12 -0
- 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
|