Revision 1a126ed1
Makefile | ||
---|---|---|
1 |
EVDIR=$(HOME)/local/libev |
|
2 |
|
|
3 |
OIINC = $(HOME)/projects/oi/ |
|
4 |
OILIB = $(HOME)/projects/oi/liboi.a |
|
5 |
|
|
6 |
V8INC = $(HOME)/src/v8/include |
|
7 |
#V8LIB = $(HOME)/src/v8/libv8_g.a |
|
8 |
V8LIB = $(HOME)/src/v8/libv8.a |
|
9 |
|
|
10 |
CFLAGS = -g -I$(V8INC) -I$(OIINC) -DHAVE_GNUTLS=0 -Ideps/ebb |
|
11 |
LDFLAGS = -lev -pthread # -lefence |
|
12 |
|
|
13 |
ifdef EVDIR |
|
14 |
CFLAGS += -I$(EVDIR)/include |
|
15 |
LDFLAGS += -L$(EVDIR)/lib |
|
16 |
endif |
|
17 |
|
|
18 |
node: node.o node_tcp.o node_http.o node_timer.o ebb_request_parser.o |
|
19 |
g++ -o node $^ $(LDFLAGS) $(V8LIB) $(OILIB) |
|
20 |
|
|
21 |
node.o: node.cc |
|
22 |
g++ $(CFLAGS) -c $< |
|
23 |
|
|
24 |
node_tcp.o: node_tcp.cc |
|
25 |
g++ $(CFLAGS) -c $< |
|
26 |
|
|
27 |
node_http.o: node_http.cc |
|
28 |
g++ $(CFLAGS) -c $< |
|
29 |
|
|
30 |
node_timer.o: node_timer.cc |
|
31 |
g++ $(CFLAGS) -c $< |
|
32 |
|
|
33 |
ebb_request_parser.o: ebb_request_parser.c deps/ebb/ebb_request_parser.h |
|
34 |
g++ $(CFLAGS) -c $< |
|
35 |
|
|
36 |
ebb_request_parser.c: deps/ebb/ebb_request_parser.rl |
|
37 |
ragel -s -G2 $< -o $@ |
|
38 |
|
|
39 |
PASS="\033[1;32mPASS\033[0m\n" |
|
40 |
FAIL="\033[1;31mFAIL\033[0m\n" |
|
41 |
|
|
42 |
test: node test/test_* |
|
43 |
@for i in test/test_*; do \ |
|
44 |
if [ -x $$i ]; then \ |
|
45 |
echo "\n\033[1m$$i\033[0m"; \ |
|
46 |
./$$i && echo $(PASS) || echo $(FAIL); \ |
|
47 |
fi \ |
|
48 |
done |
|
49 |
|
|
50 |
clean: |
|
51 |
rm -f ebb_request_parser.c |
|
52 |
rm -f *.o |
|
53 |
rm -f node |
|
54 |
|
|
55 |
.PHONY: clean test |
deps/libeio/wscript | ||
---|---|---|
1 |
import Options |
|
2 |
|
|
3 |
def set_options(opt): |
|
4 |
pass |
|
5 |
#opt.tool_options('compiler_cc') |
|
6 |
|
|
7 |
def configure(conf): |
|
8 |
print "--- libeio ---" |
|
9 |
#conf.check_tool('compiler_cc') |
|
10 |
|
|
11 |
conf.check_cc(lib="pthread", header_name="pthread.h", function_name="pthread_create", mandatory=True) |
|
12 |
|
|
13 |
platform_string = "__" + Options.platform |
|
14 |
if Options.platform == "linux2": |
|
15 |
platform_string = "__linux" |
|
16 |
conf.define(platform_string, 1) |
|
17 |
|
|
18 |
conf.check_cc(msg="Checking for futimes(2)", define_name="HAVE_FUTIMES", fragment=""" |
|
19 |
#include <sys/types.h> |
|
20 |
#include <sys/time.h> |
|
21 |
#include <utime.h> |
|
22 |
struct timeval tv[2]; |
|
23 |
int res; |
|
24 |
int fd; |
|
25 |
int main(void) |
|
26 |
{ |
|
27 |
res = futimes (fd, tv); |
|
28 |
return 0; |
|
29 |
} |
|
30 |
""") |
|
31 |
|
|
32 |
conf.check_cc(msg="Checking for readahead(2)", define_name="HAVE_READAHEAD", fragment=""" |
|
33 |
#include <fcntl.h> |
|
34 |
int main(void) |
|
35 |
{ |
|
36 |
int fd = 0; |
|
37 |
size_t count = 2; |
|
38 |
ssize_t res; |
|
39 |
res = readahead (fd, 0, count); |
|
40 |
return 0; |
|
41 |
} |
|
42 |
""") |
|
43 |
|
|
44 |
conf.check_cc(msg="Checking for fdatasync(2)", define_name="HAVE_FDATASYNC", fragment=""" |
|
45 |
#include <unistd.h> |
|
46 |
int main(void) |
|
47 |
{ |
|
48 |
int fd = 0; |
|
49 |
fdatasync (fd); |
|
50 |
return 0; |
|
51 |
} |
|
52 |
""") |
|
53 |
|
|
54 |
conf.check_cc(msg="Checking for pread(2) and pwrite(2)", define_name="HAVE_PREADWRITE", fragment=""" |
|
55 |
#include <unistd.h> |
|
56 |
int main(void) |
|
57 |
{ |
|
58 |
int fd = 0; |
|
59 |
size_t count = 1; |
|
60 |
char buf; |
|
61 |
off_t offset = 1; |
|
62 |
ssize_t res; |
|
63 |
res = pread (fd, &buf, count, offset); |
|
64 |
res = pwrite (fd, &buf, count, offset); |
|
65 |
return 0; |
|
66 |
} |
|
67 |
""") |
|
68 |
|
|
69 |
conf.check_cc(msg="Checking for sendfile(2)" , defines=[platform_string + "=1"] , define_name="HAVE_SENDFILE" , fragment=""" |
|
70 |
# include <sys/types.h> |
|
71 |
#if __linux |
|
72 |
# include <sys/sendfile.h> |
|
73 |
#elif __freebsd |
|
74 |
# include <sys/socket.h> |
|
75 |
# include <sys/uio.h> |
|
76 |
#elif __hpux |
|
77 |
# include <sys/socket.h> |
|
78 |
#else |
|
79 |
# error unsupported architecture |
|
80 |
#endif |
|
81 |
int main(void) |
|
82 |
{ |
|
83 |
int fd = 0; |
|
84 |
off_t offset = 1; |
|
85 |
size_t count = 2; |
|
86 |
ssize_t res; |
|
87 |
#if __linux |
|
88 |
res = sendfile (fd, fd, offset, count); |
|
89 |
#elif __freebsd |
|
90 |
res = sendfile (fd, fd, offset, count, 0, &offset, 0); |
|
91 |
#elif __hpux |
|
92 |
res = sendfile (fd, fd, offset, count, 0, 0); |
|
93 |
#endif |
|
94 |
return 0; |
|
95 |
} |
|
96 |
""") |
|
97 |
|
|
98 |
conf.check_cc(msg="Checking for sync_file_range(2) ", fragment=""" |
|
99 |
#include <fcntl.h> |
|
100 |
int main(void) |
|
101 |
{ |
|
102 |
int fd = 0; |
|
103 |
off64_t offset = 1; |
|
104 |
off64_t nbytes = 1; |
|
105 |
unsigned int flags = SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE|SYNC_FILE_RANGE_WAIT_AFTER; |
|
106 |
ssize_t res; |
|
107 |
res = sync_file_range (fd, offset, nbytes, flags); |
|
108 |
return 0; |
|
109 |
} |
|
110 |
""", define_name="HAVE_SYNC_FILE_RANGE") |
|
111 |
|
|
112 |
conf.write_config_header('config.h') |
|
113 |
|
|
114 |
def build(bld): |
|
115 |
libeio = bld.new_task_gen("cc", "staticlib") |
|
116 |
libeio.source = "eio.c" |
|
117 |
libeio.target = 'eio' |
|
118 |
libeio.name = 'eio' |
|
119 |
libeio.includes = '. ../..' |
|
120 |
|
deps/libev/wscript | ||
---|---|---|
1 |
import Options |
|
2 |
|
|
3 |
def set_options(opt): |
|
4 |
pass |
|
5 |
#opt.tool_options('compiler_cc') |
|
6 |
|
|
7 |
def configure(conf): |
|
8 |
print "--- libev ---" |
|
9 |
#conf.check_tool('compiler_cc') |
|
10 |
|
|
11 |
platform_string = "__" + Options.platform |
|
12 |
if Options.platform == "linux2": |
|
13 |
platform_string = "__linux" |
|
14 |
conf.define(platform_string, 1) |
|
15 |
|
|
16 |
conf.check_cc(header_name="sys/inotify.h") |
|
17 |
conf.check_cc(header_name="sys/epoll.h") |
|
18 |
conf.check_cc(header_name="sys/event.h") |
|
19 |
conf.check_cc(header_name="sys/queue.h") |
|
20 |
conf.check_cc(header_name="port.h") |
|
21 |
conf.check_cc(header_name="poll.h") |
|
22 |
conf.check_cc(header_name="sys/select.h") |
|
23 |
conf.check_cc(header_name="sys/eventfd.h") |
|
24 |
conf.check_cc(header_name="sys/inotify.h", function_name="inotify_init") |
|
25 |
conf.check_cc(header_name="sys/epoll.h", function_name="epoll_ctl") |
|
26 |
conf.check_cc(header_name="sys/queue.h", function_name="kqueue") |
|
27 |
conf.check_cc(header_name="port.h", function_name="port_create") |
|
28 |
conf.check_cc(header_name="poll.h", function_name="poll") |
|
29 |
conf.check_cc(header_name="sys/select.h", function_name="select") |
|
30 |
conf.check_cc(header_name="sys/eventfd.h", function_name="eventfd") |
|
31 |
code = """ |
|
32 |
#include <syscall.h> |
|
33 |
#include <time.h> |
|
34 |
|
|
35 |
int main() { |
|
36 |
struct timespec ts; |
|
37 |
int status = syscall (SYS_clock_gettime, CLOCK_REALTIME, &ts); |
|
38 |
return 0; |
|
39 |
} |
|
40 |
""" |
|
41 |
conf.check_cc(fragment=code, define_name="HAVE_CLOCK_SYSCALL") |
|
42 |
conf.check_cc(lib="rt", header_name="time.h", function_name="clock_gettime") |
|
43 |
conf.check_cc(lib="rt", header_name="time.h", function_name="nanosleep") |
|
44 |
conf.check_cc(lib="m", header_name="math.h", function_name="ceil") |
|
45 |
|
|
46 |
conf.define("HAVE_CONFIG_H", 1) |
|
47 |
conf.write_config_header('config.h') |
|
48 |
|
|
49 |
def build(bld): |
|
50 |
libev = bld.new_task_gen("cc", "staticlib") |
|
51 |
libev.source = 'ev.c' |
|
52 |
libev.target = 'ev' |
|
53 |
libev.name = 'ev' |
|
54 |
libev.includes = '. ../..' |
|
55 |
|
node.cc | ||
---|---|---|
1 |
#include "node.h" |
|
2 |
|
|
3 |
#include "node_tcp.h" |
|
4 |
#include "node_http.h" |
|
5 |
#include "node_timer.h" |
|
6 |
|
|
7 |
#include <stdio.h> |
|
8 |
#include <assert.h> |
|
9 |
|
|
10 |
#include <string> |
|
11 |
#include <list> |
|
12 |
#include <map> |
|
13 |
|
|
14 |
using namespace v8; |
|
15 |
using namespace std; |
|
16 |
|
|
17 |
static int exit_code = 0; |
|
18 |
|
|
19 |
// Reads a file into a v8 string. |
|
20 |
static Handle<String> |
|
21 |
ReadFile (const string& name) |
|
22 |
{ |
|
23 |
|
|
24 |
FILE* file = fopen(name.c_str(), "rb"); |
|
25 |
if (file == NULL) return Handle<String>(); |
|
26 |
|
|
27 |
fseek(file, 0, SEEK_END); |
|
28 |
int size = ftell(file); |
|
29 |
rewind(file); |
|
30 |
|
|
31 |
char chars[size+1]; |
|
32 |
chars[size] = '\0'; |
|
33 |
for (int i = 0; i < size;) { |
|
34 |
int read = fread(&chars[i], 1, size - i, file); |
|
35 |
if(read <= 0) { |
|
36 |
perror("read()"); |
|
37 |
} |
|
38 |
i += read; |
|
39 |
} |
|
40 |
|
|
41 |
uint16_t expanded_base[size+1]; |
|
42 |
expanded_base[size] = '\0'; |
|
43 |
for(int i = 0; i < size; i++) |
|
44 |
expanded_base[i] = chars[i]; |
|
45 |
|
|
46 |
fclose(file); |
|
47 |
|
|
48 |
HandleScope scope; |
|
49 |
Local<String> result = String::New(expanded_base, size); |
|
50 |
|
|
51 |
return scope.Close(result); |
|
52 |
} |
|
53 |
|
|
54 |
static Handle<Value> |
|
55 |
Log (const Arguments& args) |
|
56 |
{ |
|
57 |
if (args.Length() < 1) return v8::Undefined(); |
|
58 |
HandleScope scope; |
|
59 |
Handle<Value> arg = args[0]; |
|
60 |
String::Utf8Value value(arg); |
|
61 |
|
|
62 |
printf("%s\n", *value); |
|
63 |
fflush(stdout); |
|
64 |
|
|
65 |
return Undefined(); |
|
66 |
} |
|
67 |
|
|
68 |
|
|
69 |
static Handle<Value> |
|
70 |
BlockingFileRead (const Arguments& args) |
|
71 |
{ |
|
72 |
if (args.Length() < 1) return v8::Undefined(); |
|
73 |
HandleScope scope; |
|
74 |
|
|
75 |
String::Utf8Value filename(args[0]); |
|
76 |
Handle<String> output = ReadFile (*filename); |
|
77 |
return scope.Close(output); |
|
78 |
} |
|
79 |
|
|
80 |
static void |
|
81 |
OnFatalError (const char* location, const char* message) |
|
82 |
{ |
|
83 |
fprintf(stderr, "Fatal error. %s %s\n", location, message); |
|
84 |
ev_unloop(node_loop(), EVUNLOOP_ALL); |
|
85 |
} |
|
86 |
|
|
87 |
|
|
88 |
// Extracts a C string from a V8 Utf8Value. |
|
89 |
const char* ToCString(const v8::String::Utf8Value& value) { |
|
90 |
return *value ? *value : "<string conversion failed>"; |
|
91 |
} |
|
92 |
|
|
93 |
void ReportException(v8::TryCatch* try_catch) { |
|
94 |
v8::HandleScope handle_scope; |
|
95 |
v8::String::Utf8Value exception(try_catch->Exception()); |
|
96 |
const char* exception_string = ToCString(exception); |
|
97 |
v8::Handle<v8::Message> message = try_catch->Message(); |
|
98 |
if (message.IsEmpty()) { |
|
99 |
// V8 didn't provide any extra information about this error; just |
|
100 |
// print the exception. |
|
101 |
printf("%s\n", exception_string); |
|
102 |
} else { |
|
103 |
message->PrintCurrentStackTrace(stdout); |
|
104 |
|
|
105 |
// Print (filename):(line number): (message). |
|
106 |
v8::String::Utf8Value filename(message->GetScriptResourceName()); |
|
107 |
const char* filename_string = ToCString(filename); |
|
108 |
int linenum = message->GetLineNumber(); |
|
109 |
printf("%s:%i: %s\n", filename_string, linenum, exception_string); |
|
110 |
// Print line of source code. |
|
111 |
v8::String::Utf8Value sourceline(message->GetSourceLine()); |
|
112 |
const char* sourceline_string = ToCString(sourceline); |
|
113 |
printf("%s\n", sourceline_string); |
|
114 |
// Print wavy underline (GetUnderline is deprecated). |
|
115 |
int start = message->GetStartColumn(); |
|
116 |
for (int i = 0; i < start; i++) { |
|
117 |
printf(" "); |
|
118 |
} |
|
119 |
int end = message->GetEndColumn(); |
|
120 |
for (int i = start; i < end; i++) { |
|
121 |
printf("^"); |
|
122 |
} |
|
123 |
printf("\n"); |
|
124 |
} |
|
125 |
} |
|
126 |
|
|
127 |
void |
|
128 |
node_fatal_exception (TryCatch &try_catch) |
|
129 |
{ |
|
130 |
ReportException(&try_catch); |
|
131 |
ev_unloop(node_loop(), EVUNLOOP_ALL); |
|
132 |
exit_code = 1; |
|
133 |
} |
|
134 |
|
|
135 |
|
|
136 |
// Executes a string within the current v8 context. |
|
137 |
bool ExecuteString(v8::Handle<v8::String> source, |
|
138 |
v8::Handle<v8::Value> name, |
|
139 |
bool print_result, |
|
140 |
bool report_exceptions) { |
|
141 |
v8::HandleScope handle_scope; |
|
142 |
v8::TryCatch try_catch; |
|
143 |
v8::Handle<v8::Script> script = v8::Script::Compile(source, name); |
|
144 |
if (script.IsEmpty()) { |
|
145 |
// Print errors that happened during compilation. |
|
146 |
if (report_exceptions) |
|
147 |
ReportException(&try_catch); |
|
148 |
return false; |
|
149 |
} else { |
|
150 |
v8::Handle<v8::Value> result = script->Run(); |
|
151 |
if (result.IsEmpty()) { |
|
152 |
// Print errors that happened during execution. |
|
153 |
if (report_exceptions) |
|
154 |
ReportException(&try_catch); |
|
155 |
return false; |
|
156 |
} else { |
|
157 |
if (print_result && !result->IsUndefined()) { |
|
158 |
// If all went well and the result wasn't undefined then print |
|
159 |
// the returned value. |
|
160 |
v8::String::Utf8Value str(result); |
|
161 |
const char* cstr = ToCString(str); |
|
162 |
printf("%s\n", cstr); |
|
163 |
} |
|
164 |
return true; |
|
165 |
} |
|
166 |
} |
|
167 |
} |
|
168 |
|
|
169 |
// The callback that is invoked by v8 whenever the JavaScript 'load' |
|
170 |
// function is called. Loads, compiles and executes its argument |
|
171 |
// JavaScript file. |
|
172 |
v8::Handle<v8::Value> Load(const v8::Arguments& args) { |
|
173 |
for (int i = 0; i < args.Length(); i++) { |
|
174 |
v8::HandleScope handle_scope; |
|
175 |
v8::String::Utf8Value file(args[i]); |
|
176 |
if (*file == NULL) { |
|
177 |
return v8::ThrowException(v8::String::New("Error loading file")); |
|
178 |
} |
|
179 |
v8::Handle<v8::String> source = ReadFile(*file); |
|
180 |
if (source.IsEmpty()) { |
|
181 |
return v8::ThrowException(v8::String::New("Error loading file")); |
|
182 |
} |
|
183 |
if (!ExecuteString(source, v8::String::New(*file), false, true)) { |
|
184 |
return v8::ThrowException(v8::String::New("Error executing file")); |
|
185 |
} |
|
186 |
} |
|
187 |
return v8::Undefined(); |
|
188 |
} |
|
189 |
|
|
190 |
int |
|
191 |
main (int argc, char *argv[]) |
|
192 |
{ |
|
193 |
V8::SetFlagsFromCommandLine(&argc, argv, true); |
|
194 |
|
|
195 |
if(argc != 2) { |
|
196 |
fprintf(stderr, "No script was specified.\n"); |
|
197 |
return 1; |
|
198 |
} |
|
199 |
string filename(argv[1]); |
|
200 |
|
|
201 |
HandleScope handle_scope; |
|
202 |
|
|
203 |
Persistent<Context> context = Context::New(NULL, ObjectTemplate::New()); |
|
204 |
Context::Scope context_scope(context); |
|
205 |
|
|
206 |
Local<Object> g = Context::GetCurrent()->Global(); |
|
207 |
|
|
208 |
g->Set ( String::New("log") |
|
209 |
, FunctionTemplate::New(Log)->GetFunction() |
|
210 |
); |
|
211 |
|
|
212 |
g->Set ( String::New("load") |
|
213 |
, FunctionTemplate::New(Load)->GetFunction() |
|
214 |
); |
|
215 |
|
|
216 |
g->Set ( String::New("blockingFileRead") |
|
217 |
, FunctionTemplate::New(BlockingFileRead)->GetFunction() |
|
218 |
); |
|
219 |
|
|
220 |
Init_timer(g); |
|
221 |
Init_tcp(g); |
|
222 |
Init_http(g); |
|
223 |
|
|
224 |
V8::SetFatalErrorHandler(OnFatalError); |
|
225 |
|
|
226 |
v8::Handle<v8::String> source = ReadFile(filename); |
|
227 |
ExecuteString(source, String::New(filename.c_str()), false, true); |
|
228 |
|
|
229 |
ev_loop(node_loop(), 0); |
|
230 |
|
|
231 |
context.Dispose(); |
|
232 |
|
|
233 |
return exit_code; |
|
234 |
} |
|
235 |
|
node.h | ||
---|---|---|
1 |
#ifndef node_h |
|
2 |
#define node_h |
|
3 |
|
|
4 |
#include <ev.h> |
|
5 |
#include <v8.h> |
|
6 |
|
|
7 |
void node_fatal_exception (v8::TryCatch &try_catch); |
|
8 |
#define node_loop() ev_default_loop(0) |
|
9 |
|
|
10 |
#endif // node_h |
|
11 |
|
node_http.cc | ||
---|---|---|
1 |
#include "node_http.h" |
|
2 |
#include "node.h" |
|
3 |
|
|
4 |
#include <oi_socket.h> |
|
5 |
#include <ebb_request_parser.h> |
|
6 |
|
|
7 |
#include <string> |
|
8 |
#include <list> |
|
9 |
|
|
10 |
using namespace v8; |
|
11 |
using namespace std; |
|
12 |
|
|
13 |
static Persistent<ObjectTemplate> request_template; |
|
14 |
|
|
15 |
// globals |
|
16 |
static Persistent<String> path_str; |
|
17 |
static Persistent<String> uri_str; |
|
18 |
static Persistent<String> query_string_str; |
|
19 |
static Persistent<String> fragment_str; |
|
20 |
static Persistent<String> method_str; |
|
21 |
static Persistent<String> http_version_str; |
|
22 |
static Persistent<String> headers_str; |
|
23 |
|
|
24 |
static Persistent<String> on_request_str; |
|
25 |
static Persistent<String> on_body_str; |
|
26 |
static Persistent<String> respond_str; |
|
27 |
|
|
28 |
static Persistent<String> copy_str; |
|
29 |
static Persistent<String> delete_str; |
|
30 |
static Persistent<String> get_str; |
|
31 |
static Persistent<String> head_str; |
|
32 |
static Persistent<String> lock_str; |
|
33 |
static Persistent<String> mkcol_str; |
|
34 |
static Persistent<String> move_str; |
|
35 |
static Persistent<String> options_str; |
|
36 |
static Persistent<String> post_str; |
|
37 |
static Persistent<String> propfind_str; |
|
38 |
static Persistent<String> proppatch_str; |
|
39 |
static Persistent<String> put_str; |
|
40 |
static Persistent<String> trace_str; |
|
41 |
static Persistent<String> unlock_str; |
|
42 |
|
|
43 |
#define INVALID_STATE_ERR 1 |
|
44 |
|
|
45 |
class Server { |
|
46 |
public: |
|
47 |
Server (Handle<Object> _js_server); |
|
48 |
~Server (); |
|
49 |
|
|
50 |
int Start(struct addrinfo *servinfo); |
|
51 |
void Stop(); |
|
52 |
|
|
53 |
Handle<Value> Callback() |
|
54 |
{ |
|
55 |
HandleScope scope; |
|
56 |
Handle<Value> value = js_server->Get(on_request_str); |
|
57 |
return scope.Close(value); |
|
58 |
} |
|
59 |
|
|
60 |
private: |
|
61 |
oi_server server; |
|
62 |
Persistent<Object> js_server; |
|
63 |
}; |
|
64 |
|
|
65 |
class HttpRequest; |
|
66 |
|
|
67 |
class Connection { |
|
68 |
public: |
|
69 |
Connection(); |
|
70 |
~Connection(); |
|
71 |
|
|
72 |
void Parse(const void *buf, size_t count); |
|
73 |
void Write(); |
|
74 |
void Close(); |
|
75 |
void AddRequest (HttpRequest *request); |
|
76 |
|
|
77 |
oi_socket socket; |
|
78 |
Persistent<Function> js_onrequest; |
|
79 |
|
|
80 |
private: |
|
81 |
ebb_request_parser parser; |
|
82 |
list<HttpRequest*> requests; |
|
83 |
list<HttpRequest*> finished_requests; |
|
84 |
friend class Server; |
|
85 |
}; |
|
86 |
|
|
87 |
class HttpRequest { |
|
88 |
public: |
|
89 |
HttpRequest (Connection &c); |
|
90 |
/* Deleted from C++ as soon as possible. |
|
91 |
* Javascript object might linger. This is okay |
|
92 |
*/ |
|
93 |
~HttpRequest(); |
|
94 |
|
|
95 |
void MakeBodyCallback (const char *base, size_t length); |
|
96 |
Local<Object> CreateJSObject (); |
|
97 |
void Respond (Handle<Value> data); |
|
98 |
|
|
99 |
string path; |
|
100 |
string query_string; |
|
101 |
string fragment; |
|
102 |
string uri; |
|
103 |
|
|
104 |
list<string> header_fields; |
|
105 |
list<string> header_values; |
|
106 |
|
|
107 |
Connection &connection; |
|
108 |
ebb_request parser_info; |
|
109 |
|
|
110 |
list<oi_buf*> output; |
|
111 |
bool done; |
|
112 |
Persistent<Object> js_object; |
|
113 |
}; |
|
114 |
|
|
115 |
static Handle<Value> |
|
116 |
GetMethodString (int method) |
|
117 |
{ |
|
118 |
switch(method) { |
|
119 |
case EBB_COPY: return copy_str; |
|
120 |
case EBB_DELETE: return delete_str; |
|
121 |
case EBB_GET: return get_str; |
|
122 |
case EBB_HEAD: return head_str; |
|
123 |
case EBB_LOCK: return lock_str; |
|
124 |
case EBB_MKCOL: return mkcol_str; |
|
125 |
case EBB_MOVE: return move_str; |
|
126 |
case EBB_OPTIONS: return options_str; |
|
127 |
case EBB_POST: return post_str; |
|
128 |
case EBB_PROPFIND: return propfind_str; |
|
129 |
case EBB_PROPPATCH: return proppatch_str; |
|
130 |
case EBB_PUT: return put_str; |
|
131 |
case EBB_TRACE: return trace_str; |
|
132 |
case EBB_UNLOCK: return unlock_str; |
|
133 |
} |
|
134 |
return Null(); |
|
135 |
} |
|
136 |
|
|
137 |
static Handle<Value> |
|
138 |
RespondCallback (const Arguments& args) |
|
139 |
{ |
|
140 |
HandleScope scope; |
|
141 |
|
|
142 |
Handle<Value> v = args.Holder()->GetInternalField(0); |
|
143 |
if(v->IsUndefined()) { |
|
144 |
// check that args.Holder()->GetInternalField(0) |
|
145 |
// is not NULL if so raise INVALID_STATE_ERR |
|
146 |
printf("null request external\n"); |
|
147 |
ThrowException(Integer::New(INVALID_STATE_ERR)); |
|
148 |
return Undefined(); |
|
149 |
} |
|
150 |
Handle<External> field = Handle<External>::Cast(v); |
|
151 |
HttpRequest* request = static_cast<HttpRequest*>(field->Value()); |
|
152 |
request->Respond(args[0]); |
|
153 |
return Undefined(); |
|
154 |
} |
|
155 |
|
|
156 |
void |
|
157 |
HttpRequest::Respond (Handle<Value> data) |
|
158 |
{ |
|
159 |
if(data == Null()) { |
|
160 |
done = true; |
|
161 |
} else { |
|
162 |
Handle<String> s = data->ToString(); |
|
163 |
oi_buf *buf = oi_buf_new2(s->Length()); |
|
164 |
|
|
165 |
uint16_t expanded[s->Length()]; |
|
166 |
s->Write(expanded, 0, s->Length()); |
|
167 |
|
|
168 |
for(int i = 0; i < s->Length(); i++) { |
|
169 |
buf->base[i] = expanded[i]; |
|
170 |
} |
|
171 |
|
|
172 |
output.push_back(buf); |
|
173 |
} |
|
174 |
connection.Write(); |
|
175 |
} |
|
176 |
|
|
177 |
|
|
178 |
static void |
|
179 |
on_path (ebb_request *req, const char *buf, size_t len) |
|
180 |
{ |
|
181 |
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
182 |
request->path.append(buf, len); |
|
183 |
} |
|
184 |
|
|
185 |
static void |
|
186 |
on_uri (ebb_request *req, const char *buf, size_t len) |
|
187 |
{ |
|
188 |
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
189 |
request->uri.append(buf, len); |
|
190 |
} |
|
191 |
|
|
192 |
static void |
|
193 |
on_query_string (ebb_request *req, const char *buf, size_t len) |
|
194 |
{ |
|
195 |
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
196 |
request->query_string.append(buf, len); |
|
197 |
} |
|
198 |
|
|
199 |
static void |
|
200 |
on_fragment (ebb_request *req, const char *buf, size_t len) |
|
201 |
{ |
|
202 |
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
203 |
request->fragment.append(buf, len); |
|
204 |
} |
|
205 |
|
|
206 |
static const char upcase[] = |
|
207 |
"\0______________________________" |
|
208 |
"_________________0123456789_____" |
|
209 |
"__ABCDEFGHIJKLMNOPQRSTUVWXYZ____" |
|
210 |
"__ABCDEFGHIJKLMNOPQRSTUVWXYZ____" |
|
211 |
"________________________________" |
|
212 |
"________________________________" |
|
213 |
"________________________________" |
|
214 |
"________________________________"; |
|
215 |
|
|
216 |
static void |
|
217 |
on_header_field (ebb_request *req, const char *buf, size_t len, int header_index) |
|
218 |
{ |
|
219 |
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
220 |
|
|
221 |
char upbuf[len]; |
|
222 |
|
|
223 |
for(int i = 0; i < len; i++) |
|
224 |
upbuf[i] = upcase[buf[i]]; |
|
225 |
|
|
226 |
if( request->header_fields.size() == header_index - 1) { |
|
227 |
request->header_fields.back().append(upbuf, len); |
|
228 |
} else { |
|
229 |
request->header_fields.push_back( string(upbuf, len) ); |
|
230 |
} |
|
231 |
} |
|
232 |
|
|
233 |
static void |
|
234 |
on_header_value (ebb_request *req, const char *buf, size_t len, int header_index) |
|
235 |
{ |
|
236 |
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
237 |
|
|
238 |
if( request->header_values.size() == header_index - 1) { |
|
239 |
request->header_values.back().append(buf, len); |
|
240 |
} else { |
|
241 |
request->header_values.push_back( string(buf, len) ); |
|
242 |
} |
|
243 |
} |
|
244 |
|
|
245 |
static void |
|
246 |
on_headers_complete (ebb_request *req) |
|
247 |
{ |
|
248 |
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
249 |
|
|
250 |
HandleScope scope; |
|
251 |
|
|
252 |
Handle<Object> js_request = request->CreateJSObject(); |
|
253 |
|
|
254 |
// Set up an exception handler before calling the Process function |
|
255 |
TryCatch try_catch; |
|
256 |
|
|
257 |
// Invoke the process function, giving the global object as 'this' |
|
258 |
// and one argument, the request. |
|
259 |
const int argc = 1; |
|
260 |
Handle<Value> argv[argc] = { js_request }; |
|
261 |
Handle<Value> r = request->connection.js_onrequest->Call(Context::GetCurrent()->Global(), argc, argv); |
|
262 |
|
|
263 |
if(try_catch.HasCaught()) |
|
264 |
node_fatal_exception(try_catch); |
|
265 |
} |
|
266 |
|
|
267 |
static void |
|
268 |
on_request_complete (ebb_request *req) |
|
269 |
{ |
|
270 |
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
271 |
request->MakeBodyCallback(NULL, 0); // EOF |
|
272 |
} |
|
273 |
|
|
274 |
static void |
|
275 |
on_body (ebb_request *req, const char *base, size_t length) |
|
276 |
{ |
|
277 |
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
278 |
|
|
279 |
if(length) |
|
280 |
request->MakeBodyCallback(base, length); |
|
281 |
} |
|
282 |
|
|
283 |
static ebb_request * on_request |
|
284 |
( void *data |
|
285 |
) |
|
286 |
{ |
|
287 |
Connection *connection = static_cast<Connection*> (data); |
|
288 |
|
|
289 |
HttpRequest *request = new HttpRequest(*connection); |
|
290 |
connection->AddRequest(request); |
|
291 |
|
|
292 |
return &request->parser_info; |
|
293 |
} |
|
294 |
|
|
295 |
static void on_read |
|
296 |
( oi_socket *socket |
|
297 |
, const void *buf |
|
298 |
, size_t count |
|
299 |
) |
|
300 |
{ |
|
301 |
Connection *connection = static_cast<Connection*> (socket->data); |
|
302 |
if(count == 0) { |
|
303 |
connection->Close(); |
|
304 |
} else { |
|
305 |
//write(1, buf, count); |
|
306 |
connection->Parse(buf, count); |
|
307 |
} |
|
308 |
} |
|
309 |
|
|
310 |
static void on_close |
|
311 |
( oi_socket *socket |
|
312 |
) |
|
313 |
{ |
|
314 |
Connection *connection = static_cast<Connection*> (socket->data); |
|
315 |
delete connection; |
|
316 |
} |
|
317 |
|
|
318 |
HttpRequest::~HttpRequest () |
|
319 |
{ |
|
320 |
HandleScope scope; // needed? |
|
321 |
// delete a reference c++ HttpRequest |
|
322 |
js_object->SetInternalField(0, Undefined()); |
|
323 |
js_object->Delete(respond_str); |
|
324 |
// dispose of Persistent handle so that |
|
325 |
// it can be GC'd normally. |
|
326 |
js_object.Dispose(); |
|
327 |
} |
|
328 |
|
|
329 |
HttpRequest::HttpRequest (Connection &c) : connection(c) |
|
330 |
{ |
|
331 |
ebb_request_init(&parser_info); |
|
332 |
parser_info.on_path = on_path; |
|
333 |
parser_info.on_query_string = on_query_string; |
|
334 |
parser_info.on_uri = on_uri; |
|
335 |
parser_info.on_fragment = on_fragment; |
|
336 |
parser_info.on_header_field = on_header_field; |
|
337 |
parser_info.on_header_value = on_header_value; |
|
338 |
parser_info.on_headers_complete = on_headers_complete; |
|
339 |
parser_info.on_body = on_body; |
|
340 |
parser_info.on_complete = on_request_complete; |
|
341 |
parser_info.data = this; |
|
342 |
|
|
343 |
done = false; |
|
344 |
} |
|
345 |
|
|
346 |
void |
|
347 |
HttpRequest::MakeBodyCallback (const char *base, size_t length) |
|
348 |
{ |
|
349 |
HandleScope handle_scope; |
|
350 |
|
|
351 |
Handle<Value> onbody_val = js_object->Get(on_body_str); |
|
352 |
if (!onbody_val->IsFunction()) return; |
|
353 |
Handle<Function> onbody = Handle<Function>::Cast(onbody_val); |
|
354 |
|
|
355 |
TryCatch try_catch; |
|
356 |
const int argc = 1; |
|
357 |
Handle<Value> argv[argc]; |
|
358 |
|
|
359 |
if(length) { |
|
360 |
// TODO ByteArray? |
|
361 |
// |
|
362 |
|
|
363 |
uint16_t expanded_base[length]; |
|
364 |
for(int i = 0; i < length; i++) { |
|
365 |
expanded_base[i] = base[i]; |
|
366 |
} |
|
367 |
|
|
368 |
Handle<String> chunk = String::New(expanded_base, length); |
|
369 |
argv[0] = chunk; |
|
370 |
} else { |
|
371 |
argv[0] = Null(); |
|
372 |
} |
|
373 |
|
|
374 |
Handle<Value> result = onbody->Call(js_object, argc, argv); |
|
375 |
|
|
376 |
if(try_catch.HasCaught()) |
|
377 |
node_fatal_exception(try_catch); |
|
378 |
} |
|
379 |
|
|
380 |
Local<Object> |
|
381 |
HttpRequest::CreateJSObject () |
|
382 |
{ |
|
383 |
HandleScope scope; |
|
384 |
|
|
385 |
if (request_template.IsEmpty()) { |
|
386 |
Handle<ObjectTemplate> raw_template = ObjectTemplate::New(); |
|
387 |
raw_template->SetInternalFieldCount(1); |
|
388 |
raw_template->Set(respond_str, FunctionTemplate::New(RespondCallback)); |
|
389 |
|
|
390 |
request_template = Persistent<ObjectTemplate>::New(raw_template); |
|
391 |
} |
|
392 |
|
|
393 |
// Create an empty http request wrapper. |
|
394 |
Handle<Object> result = request_template->NewInstance(); |
|
395 |
|
|
396 |
// Wrap the raw C++ pointer in an External so it can be referenced |
|
397 |
// from within JavaScript. |
|
398 |
Handle<External> request_ptr = External::New(this); |
|
399 |
|
|
400 |
// Store the request pointer in the JavaScript wrapper. |
|
401 |
result->SetInternalField(0, request_ptr); |
|
402 |
|
|
403 |
result->Set ( path_str |
|
404 |
, String::New(path.c_str(), path.length()) |
|
405 |
); |
|
406 |
|
|
407 |
result->Set ( uri_str |
|
408 |
, String::New(uri.c_str(), uri.length()) |
|
409 |
); |
|
410 |
|
|
411 |
result->Set ( query_string_str |
|
412 |
, String::New(query_string.c_str(), query_string.length()) |
|
413 |
); |
|
414 |
|
|
415 |
result->Set ( fragment_str |
|
416 |
, String::New(fragment.c_str(), fragment.length()) |
|
417 |
); |
|
418 |
|
|
419 |
result->Set ( method_str |
|
420 |
, GetMethodString(parser_info.method) |
|
421 |
); |
|
422 |
|
|
423 |
char version[10]; |
|
424 |
snprintf ( version |
|
425 |
, 10 // big enough? :) |
|
426 |
, "%d.%d" |
|
427 |
, parser_info.version_major |
|
428 |
, parser_info.version_minor |
|
429 |
); |
|
430 |
result->Set ( http_version_str |
|
431 |
, String::New(version) |
|
432 |
); |
|
433 |
|
|
434 |
|
|
435 |
Handle<Object> headers = Object::New(); |
|
436 |
list<string>::iterator field_iterator = header_fields.begin(); |
|
437 |
list<string>::iterator value_iterator = header_values.begin(); |
|
438 |
while( value_iterator != header_values.end() ) { |
|
439 |
string &f = *field_iterator; |
|
440 |
string &v = *value_iterator; |
|
441 |
|
|
442 |
headers->Set( String::NewSymbol(f.c_str(), f.length()) |
|
443 |
, String::New(v.c_str(), v.length() ) |
|
444 |
); |
|
445 |
|
|
446 |
field_iterator++; |
|
447 |
value_iterator++; |
|
448 |
} |
|
449 |
result->Set(headers_str, headers); |
|
450 |
|
|
451 |
js_object = Persistent<Object>::New(result); |
|
452 |
// XXX does the request's js_object need a MakeWeak callback? |
|
453 |
// i dont think so because at some point the connection closes |
|
454 |
// and we're going to delete the request. |
|
455 |
|
|
456 |
return scope.Close(result); |
|
457 |
} |
|
458 |
|
|
459 |
|
|
460 |
static oi_socket* |
|
461 |
on_connection (oi_server *_server, struct sockaddr *addr, socklen_t len) |
|
462 |
{ |
|
463 |
HandleScope scope; |
|
464 |
|
|
465 |
Server *server = static_cast<Server*> (_server->data); |
|
466 |
|
|
467 |
Handle<Value> callback_v = server->Callback(); |
|
468 |
|
|
469 |
if(callback_v == Undefined()) |
|
470 |
return NULL; |
|
471 |
|
|
472 |
Connection *connection = new Connection(); |
|
473 |
|
|
474 |
Handle<Function> f = Handle<Function>::Cast(callback_v); |
|
475 |
connection->js_onrequest = Persistent<Function>::New(f); |
|
476 |
|
|
477 |
return &connection->socket; |
|
478 |
} |
|
479 |
|
|
480 |
Connection::Connection () |
|
481 |
{ |
|
482 |
oi_socket_init (&socket, 30.0); // TODO make timeout adjustable |
|
483 |
socket.on_read = on_read; |
|
484 |
socket.on_error = NULL; |
|
485 |
socket.on_close = on_close; |
|
486 |
socket.on_timeout = NULL; |
|
487 |
socket.on_drain = NULL; |
|
488 |
socket.data = this; |
|
489 |
|
|
490 |
ebb_request_parser_init (&parser); |
|
491 |
parser.new_request = on_request; |
|
492 |
parser.data = this; |
|
493 |
} |
|
494 |
|
|
495 |
Connection::~Connection () |
|
496 |
{ |
|
497 |
list<HttpRequest*>::iterator it = requests.begin(); |
|
498 |
|
|
499 |
// delete all the requests |
|
500 |
|
|
501 |
for(it = requests.begin(); it != requests.end(); it++) |
|
502 |
delete *it; |
|
503 |
|
|
504 |
for(it = finished_requests.begin(); it != finished_requests.end(); it++) |
|
505 |
delete *it; |
|
506 |
} |
|
507 |
|
|
508 |
void |
|
509 |
Connection::Parse(const void *buf, size_t count) |
|
510 |
{ |
|
511 |
// FIXME change ebb_request_parser to have void* arg |
|
512 |
ebb_request_parser_execute ( &parser |
|
513 |
, static_cast<const char*> (buf) |
|
514 |
, count |
|
515 |
); |
|
516 |
|
|
517 |
if(ebb_request_parser_has_error(&parser)) { |
|
518 |
fprintf(stderr, "parse error closing connection\n"); |
|
519 |
oi_socket_close(&socket); |
|
520 |
} |
|
521 |
} |
|
522 |
|
|
523 |
void |
|
524 |
Connection::AddRequest(HttpRequest *request) |
|
525 |
{ |
|
526 |
requests.push_back(request); |
|
527 |
} |
|
528 |
|
|
529 |
void |
|
530 |
Connection::Write ( ) |
|
531 |
{ |
|
532 |
if(requests.size() == 0) |
|
533 |
return; |
|
534 |
|
|
535 |
HttpRequest *request = requests.front(); |
|
536 |
|
|
537 |
while(request->output.size() > 0) { |
|
538 |
oi_buf *buf = request->output.front(); |
|
539 |
oi_socket_write(&socket, buf); |
|
540 |
request->output.pop_front(); |
|
541 |
} |
|
542 |
|
|
543 |
if(request->done) { |
|
544 |
if(!ebb_request_should_keep_alive(&request->parser_info)) { |
|
545 |
socket.on_drain = oi_socket_close; |
|
546 |
} |
|
547 |
|
|
548 |
requests.pop_front(); |
|
549 |
finished_requests.push_back(request); |
|
550 |
|
|
551 |
Write(); |
|
552 |
} |
|
553 |
} |
|
554 |
|
|
555 |
void |
|
556 |
Connection::Close ( ) |
|
557 |
{ |
|
558 |
oi_socket_close(&socket); |
|
559 |
} |
|
560 |
|
|
561 |
static void |
|
562 |
server_destroy (Persistent<Value> _, void *data) |
|
563 |
{ |
|
564 |
Server *server = static_cast<Server *> (data); |
|
565 |
delete server; |
|
566 |
} |
|
567 |
|
|
568 |
Server::Server (Handle<Object> _js_server) |
|
569 |
{ |
|
570 |
oi_server_init(&server, 1024); |
|
571 |
server.on_connection = on_connection; |
|
572 |
server.data = this; |
|
573 |
HandleScope scope; |
|
574 |
js_server = Persistent<Object>::New (_js_server); |
|
575 |
// are we ever going to need this external? |
|
576 |
js_server->SetInternalField (0, External::New(this)); |
|
577 |
js_server.MakeWeak (this, server_destroy); |
|
578 |
} |
|
579 |
|
|
580 |
Server::~Server () |
|
581 |
{ |
|
582 |
Stop(); |
|
583 |
js_server.Dispose(); |
|
584 |
js_server.Clear(); // necessary? |
|
585 |
} |
|
586 |
|
|
587 |
int |
|
588 |
Server::Start(struct addrinfo *servinfo) |
|
589 |
{ |
|
590 |
int r = oi_server_listen(&server, servinfo); |
|
591 |
if(r == 0) |
|
592 |
oi_server_attach(&server, node_loop()); |
|
593 |
return r; |
|
594 |
} |
|
595 |
|
|
596 |
void |
|
597 |
Server::Stop() |
|
598 |
{ |
|
599 |
oi_server_close (&server); |
|
600 |
oi_server_detach (&server); |
|
601 |
} |
|
602 |
|
|
603 |
/* This constructor takes 2 arguments: host, port. */ |
|
604 |
static Handle<Value> |
|
605 |
newHTTPServer (const Arguments& args) |
|
606 |
{ |
|
607 |
if (args.Length() < 3) |
|
608 |
return Undefined(); |
|
609 |
|
|
610 |
HandleScope scope; |
|
611 |
|
|
612 |
char *host = NULL; |
|
613 |
String::AsciiValue host_v(args[0]->ToString()); |
|
614 |
if(args[0]->IsString()) { |
|
615 |
host = *host_v; |
|
616 |
} |
|
617 |
String::AsciiValue port(args[1]->ToString()); |
|
618 |
|
|
619 |
Handle<Function> onrequest = Handle<Function>::Cast(args[2]); |
|
620 |
args.This()->Set(on_request_str, onrequest); |
|
621 |
|
|
622 |
// get addrinfo for localhost, PORT |
|
623 |
struct addrinfo *servinfo; |
|
624 |
struct addrinfo hints; |
|
625 |
memset(&hints, 0, sizeof hints); |
|
626 |
hints.ai_family = AF_UNSPEC; |
|
627 |
hints.ai_socktype = SOCK_STREAM; |
|
628 |
hints.ai_flags = AI_PASSIVE; |
|
629 |
// FIXME BLOCKING |
|
630 |
int r = getaddrinfo(host, *port, &hints, &servinfo); |
|
631 |
if (r != 0) |
|
632 |
return Undefined(); // XXX raise error? |
|
633 |
|
|
634 |
// |
|
635 |
// |
|
636 |
// |
|
637 |
// TODO host is ignored for now assumed localhost |
|
638 |
// |
|
639 |
// |
|
640 |
// |
|
641 |
// |
|
642 |
|
|
643 |
Server *server = new Server(args.This()); |
|
644 |
if(server == NULL) |
|
645 |
return Undefined(); // XXX raise error? |
|
646 |
|
|
647 |
r = server->Start(servinfo); |
|
648 |
if (r != 0) |
|
649 |
return Undefined(); // XXX raise error? |
|
650 |
|
|
651 |
return args.This(); |
|
652 |
} |
|
653 |
|
|
654 |
void |
|
655 |
Init_http (Handle<Object> target) |
|
656 |
{ |
|
657 |
HandleScope scope; |
|
658 |
|
|
659 |
Local<FunctionTemplate> server_t = FunctionTemplate::New(newHTTPServer); |
|
660 |
server_t->InstanceTemplate()->SetInternalFieldCount(1); |
|
661 |
|
|
662 |
server_t->Set("INVALID_STATE_ERR", Integer::New(INVALID_STATE_ERR)); |
|
663 |
|
|
664 |
target->Set(String::New("HTTPServer"), server_t->GetFunction()); |
|
665 |
|
|
666 |
path_str = Persistent<String>::New( String::NewSymbol("path") ); |
|
667 |
uri_str = Persistent<String>::New( String::NewSymbol("uri") ); |
|
668 |
query_string_str = Persistent<String>::New( String::NewSymbol("query_string") ); |
|
669 |
fragment_str = Persistent<String>::New( String::NewSymbol("fragment") ); |
|
670 |
method_str = Persistent<String>::New( String::NewSymbol("method") ); |
|
671 |
http_version_str = Persistent<String>::New( String::NewSymbol("http_version") ); |
|
672 |
headers_str = Persistent<String>::New( String::NewSymbol("headers") ); |
|
673 |
|
|
674 |
on_request_str = Persistent<String>::New( String::NewSymbol("onrequest") ); |
|
675 |
on_body_str = Persistent<String>::New( String::NewSymbol("onbody") ); |
|
676 |
respond_str = Persistent<String>::New( String::NewSymbol("respond") ); |
|
677 |
|
|
678 |
copy_str = Persistent<String>::New( String::New("COPY") ); |
|
679 |
delete_str = Persistent<String>::New( String::New("DELETE") ); |
|
680 |
get_str = Persistent<String>::New( String::New("GET") ); |
|
681 |
head_str = Persistent<String>::New( String::New("HEAD") ); |
|
682 |
lock_str = Persistent<String>::New( String::New("LOCK") ); |
|
683 |
mkcol_str = Persistent<String>::New( String::New("MKCOL") ); |
|
684 |
move_str = Persistent<String>::New( String::New("MOVE") ); |
|
685 |
options_str = Persistent<String>::New( String::New("OPTIONS") ); |
|
686 |
post_str = Persistent<String>::New( String::New("POST") ); |
|
687 |
propfind_str = Persistent<String>::New( String::New("PROPFIND") ); |
|
688 |
proppatch_str = Persistent<String>::New( String::New("PROPPATCH") ); |
|
689 |
put_str = Persistent<String>::New( String::New("PUT") ); |
|
690 |
trace_str = Persistent<String>::New( String::New("TRACE") ); |
|
691 |
unlock_str = Persistent<String>::New( String::New("UNLOCK") ); |
|
692 |
} |
node_http.h | ||
---|---|---|
1 |
#ifndef node_http_h |
|
2 |
#define node_http_h |
|
3 |
|
|
4 |
#include <v8.h> |
|
5 |
|
|
6 |
void Init_http (v8::Handle<v8::Object> target); |
|
7 |
|
|
8 |
#endif |
node_tcp.cc | ||
---|---|---|
1 |
#include "node_tcp.h" |
|
2 |
#include "node.h" |
|
3 |
|
|
4 |
#include <oi_socket.h> |
|
5 |
#include <oi_buf.h> |
|
6 |
|
|
7 |
#include <assert.h> |
|
8 |
#include <sys/types.h> |
|
9 |
#include <sys/socket.h> |
|
10 |
#include <netdb.h> |
|
11 |
|
|
12 |
using namespace v8; |
|
13 |
|
|
14 |
static Persistent<String> readyState_str; |
|
15 |
|
|
16 |
static Persistent<Integer> readyState_CONNECTING; |
|
17 |
static Persistent<Integer> readyState_OPEN; |
|
18 |
static Persistent<Integer> readyState_CLOSED; |
|
19 |
|
|
20 |
enum readyState { READY_STATE_CONNECTING = 0 |
|
21 |
, READY_STATE_OPEN = 1 |
|
22 |
, READY_STATE_CLOSED = 2 |
|
23 |
} ; |
|
24 |
|
|
25 |
class TCPClient { |
|
26 |
public: |
|
27 |
TCPClient(Handle<Object> obj); |
|
28 |
~TCPClient(); |
|
29 |
|
|
30 |
int Connect(char *host, char *port); |
|
31 |
void Write (Handle<Value> arg); |
|
32 |
void Disconnect(); |
|
33 |
|
|
34 |
void OnOpen(); |
|
35 |
void OnRead(const void *buf, size_t count); |
|
36 |
void OnClose(); |
|
37 |
|
|
38 |
private: |
|
39 |
int ReadyState(); |
|
40 |
oi_socket socket; |
|
41 |
struct addrinfo *address; |
|
42 |
Persistent<Object> js_client; |
|
43 |
}; |
|
44 |
|
|
45 |
|
|
46 |
static void |
|
47 |
on_connect (oi_socket *socket) |
|
48 |
{ |
|
49 |
TCPClient *client = static_cast<TCPClient*> (socket->data); |
|
50 |
client->OnOpen(); |
|
51 |
} |
|
52 |
|
|
53 |
static void |
|
54 |
on_close (oi_socket *socket) |
|
55 |
{ |
|
56 |
TCPClient *client = static_cast<TCPClient*> (socket->data); |
|
57 |
client->OnClose(); |
|
58 |
} |
|
59 |
|
|
60 |
static void |
|
61 |
on_read (oi_socket *socket, const void *buf, size_t count) |
|
62 |
{ |
|
63 |
TCPClient *client = static_cast<TCPClient*> (socket->data); |
|
64 |
client->OnRead(buf, count); |
|
65 |
} |
|
66 |
|
|
67 |
static struct addrinfo tcp_hints = |
|
68 |
/* ai_flags */ { AI_PASSIVE |
|
69 |
/* ai_family */ , AF_UNSPEC |
|
70 |
/* ai_socktype */ , SOCK_STREAM |
|
71 |
/* ai_protocol */ , 0 |
|
72 |
/* ai_addrlen */ , 0 |
|
73 |
/* ai_addr */ , 0 |
|
74 |
/* ai_canonname */ , 0 |
|
75 |
/* ai_next */ , 0 |
|
76 |
}; |
|
77 |
|
|
78 |
static Handle<Value> newTCPClient |
|
79 |
( const Arguments& args |
|
80 |
) |
|
81 |
{ |
|
82 |
if (args.Length() < 1) |
|
83 |
return Undefined(); |
|
84 |
|
|
85 |
HandleScope scope; |
|
86 |
|
|
87 |
char *host = NULL; |
|
88 |
String::AsciiValue host_v(args[0]->ToString()); |
|
89 |
if(args[0]->IsString()) { |
|
90 |
host = *host_v; |
|
91 |
} |
|
92 |
String::AsciiValue port(args[1]); |
|
93 |
|
|
94 |
TCPClient *client = new TCPClient(args.This()); |
|
95 |
if(client == NULL) |
|
96 |
return Undefined(); // XXX raise error? |
|
97 |
|
|
98 |
int r = client->Connect(host, *port); |
|
99 |
if (r != 0) |
|
100 |
return Undefined(); // XXX raise error? |
|
101 |
|
|
102 |
|
|
103 |
return args.This(); |
|
104 |
} |
|
105 |
|
|
106 |
static TCPClient* |
|
107 |
UnwrapClient (Handle<Object> obj) |
|
108 |
{ |
|
109 |
HandleScope scope; |
|
110 |
Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0)); |
|
111 |
TCPClient* client = static_cast<TCPClient*>(field->Value()); |
|
112 |
return client; |
|
113 |
} |
|
114 |
|
|
115 |
static Handle<Value> |
|
116 |
WriteCallback (const Arguments& args) |
|
117 |
{ |
|
118 |
HandleScope scope; |
|
119 |
TCPClient *client = UnwrapClient(args.Holder()); |
|
120 |
client->Write(args[0]); |
|
121 |
return Undefined(); |
|
122 |
} |
|
123 |
|
|
124 |
static Handle<Value> |
|
125 |
DisconnectCallback (const Arguments& args) |
|
126 |
{ |
|
127 |
HandleScope scope; |
|
128 |
TCPClient *client = UnwrapClient(args.Holder()); |
|
129 |
client->Disconnect(); |
|
130 |
return Undefined(); |
|
131 |
} |
|
132 |
|
|
133 |
static void |
|
134 |
client_destroy (Persistent<Value> _, void *data) |
|
135 |
{ |
|
136 |
TCPClient *client = static_cast<TCPClient *> (data); |
|
137 |
delete client; |
|
138 |
} |
|
139 |
|
|
140 |
TCPClient::TCPClient(Handle<Object> _js_client) |
|
141 |
{ |
|
142 |
oi_socket_init(&socket, 30.0); // TODO adjustable timeout |
|
143 |
socket.on_connect = on_connect; |
|
144 |
socket.on_read = on_read; |
|
145 |
socket.on_drain = NULL; |
|
146 |
socket.on_error = NULL; |
|
147 |
socket.on_close = on_close; |
|
148 |
socket.on_timeout = on_close; |
|
149 |
socket.data = this; |
|
150 |
|
|
151 |
HandleScope scope; |
|
152 |
js_client = Persistent<Object>::New(_js_client); |
|
153 |
js_client->SetInternalField (0, External::New(this)); |
|
154 |
js_client.MakeWeak (this, client_destroy); |
|
155 |
} |
|
156 |
|
|
157 |
TCPClient::~TCPClient () |
|
158 |
{ |
|
159 |
Disconnect(); |
|
160 |
oi_socket_detach (&socket); |
|
161 |
js_client.Dispose(); |
|
162 |
js_client.Clear(); // necessary? |
|
163 |
} |
|
164 |
|
|
165 |
int |
|
166 |
TCPClient::Connect(char *host, char *port) |
|
167 |
{ |
|
168 |
int r; |
|
169 |
|
|
170 |
HandleScope scope; |
|
171 |
|
|
172 |
js_client->Set(readyState_str, readyState_CONNECTING); |
|
173 |
|
|
174 |
/* FIXME Blocking DNS resolution. Use oi_async. */ |
|
175 |
printf("resolving host: %s, port: %s\n", host, port); |
|
176 |
r = getaddrinfo (host, port, &tcp_hints, &address); |
|
177 |
if(r != 0) { |
|
178 |
perror("getaddrinfo"); |
|
179 |
return r; |
|
180 |
} |
|
181 |
|
|
182 |
r = oi_socket_connect (&socket, address); |
|
183 |
if(r != 0) { |
|
184 |
perror("oi_socket_connect"); |
|
185 |
return r; |
|
186 |
} |
|
187 |
oi_socket_attach (&socket, node_loop()); |
|
188 |
|
|
189 |
freeaddrinfo(address); |
|
190 |
address = NULL; |
|
191 |
} |
|
192 |
|
|
193 |
void TCPClient::Write (Handle<Value> arg) |
|
194 |
{ |
|
195 |
HandleScope scope; |
|
196 |
|
|
197 |
// |
|
198 |
// TODO if ReadyState() is not READY_STATE_OPEN then raise INVALID_STATE_ERR |
|
199 |
// |
|
200 |
|
|
201 |
if(arg == Null()) { |
|
202 |
|
|
203 |
oi_socket_write_eof(&socket); |
|
204 |
|
|
205 |
} else { |
|
206 |
Local<String> s = arg->ToString(); |
|
207 |
|
|
208 |
oi_buf *buf = oi_buf_new2(s->Length()); |
|
209 |
s->WriteAscii(buf->base, 0, s->Length()); |
|
210 |
|
|
211 |
oi_socket_write(&socket, buf); |
|
212 |
} |
|
213 |
} |
|
214 |
|
|
215 |
int |
|
216 |
TCPClient::ReadyState() |
|
217 |
{ |
|
218 |
return js_client->Get(readyState_str)->IntegerValue(); |
|
219 |
} |
|
220 |
|
|
221 |
void |
|
222 |
TCPClient::Disconnect() |
|
223 |
{ |
|
224 |
oi_socket_close(&socket); |
|
225 |
} |
|
226 |
|
|
227 |
void |
|
228 |
TCPClient::OnOpen() |
|
229 |
{ |
|
230 |
HandleScope scope; |
|
231 |
|
|
232 |
assert(READY_STATE_CONNECTING == ReadyState()); |
|
233 |
js_client->Set(readyState_str, readyState_OPEN); |
|
234 |
|
|
235 |
Handle<Value> onopen_value = js_client->Get( String::NewSymbol("onopen") ); |
|
236 |
if (!onopen_value->IsFunction()) |
|
237 |
return; |
|
238 |
Handle<Function> onopen = Handle<Function>::Cast(onopen_value); |
|
239 |
|
|
240 |
TryCatch try_catch; |
|
241 |
|
|
242 |
Handle<Value> r = onopen->Call(js_client, 0, NULL); |
|
243 |
|
|
244 |
if(try_catch.HasCaught()) |
|
245 |
node_fatal_exception(try_catch); |
|
246 |
} |
|
247 |
|
|
248 |
void |
|
249 |
TCPClient::OnRead(const void *buf, size_t count) |
|
250 |
{ |
|
251 |
HandleScope scope; |
|
252 |
|
|
253 |
assert(READY_STATE_OPEN == ReadyState()); |
|
254 |
|
|
255 |
Handle<Value> onread_value = js_client->Get( String::NewSymbol("onread") ); |
|
256 |
if (!onread_value->IsFunction()) return; |
|
257 |
Handle<Function> onread = Handle<Function>::Cast(onread_value); |
|
258 |
|
|
259 |
const int argc = 1; |
|
260 |
Handle<Value> argv[argc]; |
|
261 |
|
|
262 |
if(count) { |
|
263 |
Handle<String> chunk = String::New((const char*)buf, count); // TODO binary data? |
|
264 |
argv[0] = chunk; |
|
265 |
} else { |
|
266 |
// TODO eof? delete write method? |
|
267 |
argv[0] = Null(); |
|
268 |
} |
|
269 |
|
|
270 |
TryCatch try_catch; |
|
271 |
|
Also available in: Unified diff