diff --git a/README.md b/README.md index 2b612b0..cc6bb71 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ function user %#codegen % now we can launch a couple of threads, see thread1.m and thread2.m % launching returns a thread id, a small integer - t1 = launch('thread1', 24) % pass a value to this thread + t1 = launch('thread1') stllog('thread id %d', t1) t2 = launch('thread2') stllog('thread id %d', t2) @@ -62,7 +62,7 @@ function user %#codegen % launch a new thread, see thread3.m % it just waits for the semaphore, then prints a message - t3 = launch('thread3', 42); + t3 = launch('thread3'); stllog('thread id %d', t3); sleep(2); @@ -77,9 +77,9 @@ end `thread1.m` ```matlab -function thread1(arg) %#codegen +function thread1() %#codegen for i=1:10 - stllog('hello from thread1, arg=%d, id #%d', arg, self()); + stllog('hello from thread1, id #%d', self()); sleep(1) end end @@ -114,89 +114,144 @@ end The key parts of this file is the last line ```matlab -codegen user.m thread1.m -args int32(0) thread2.m thread3.m -config cfg +codegen user.m thread1.m thread2.m thread3.m -config cfg ``` -There are four files provided to `codegen`. The first is the user's "main" function which is executed when the executable is run. Then we list the three additional threads. Note that `thread1.m` has an attribute `-args int32(0)` which specifies that this function takes an `int32` argument. +There are four files provided to `codegen`. The first is the user's "main" function -- the file `user.m` -- which is executed when the executable is run. Then we list the three additional threads. The earlier lines in `make.m` simply configure the build process which is captured in the state of the `coder.config` object `cfg` which is passed as the last argument to `codegen`. Ensure that the folder `stl` is in your MATLAB path. The result is an executable `user` in the current directory which we can run -```shellsession +``` % ./user bob alice hello world -got 4 arguments +got 3 arguments arg 0: ./user arg 1: bob arg 2: alice - arg 3: 123.5 -2018-08-27 09:24:48.460991 [user] hello world -2018-08-27 09:24:48.461848 [user] hello world -2018-08-27 09:24:48.461932 [user] thread id 1 -2018-08-27 09:24:48.461953 [user] thread id 2 -2018-08-27 09:24:48.461959 [user] waiting for thread #1 -2018-08-27 09:24:48.461964 [thread1] starting posix thread (0xA1620A0) -2018-08-27 09:24:48.461968 [thread2] starting posix thread (0xA162160) -2018-08-27 09:24:48.462000 [thread2] hello from thread2, id #2 -2018-08-27 09:24:48.462022 [thread1] hello from thread1, arg=24, id #1 -2018-08-27 09:24:49.462176 [thread1] hello from thread1, arg=24, id #1 -2018-08-27 09:24:50.463095 [thread2] hello from thread2, id #2 -2018-08-27 09:24:50.463103 [thread1] hello from thread1, arg=24, id #1 -2018-08-27 09:24:51.466686 [thread1] hello from thread1, arg=24, id #1 -2018-08-27 09:24:52.466779 [thread2] hello from thread2, id #2 -2018-08-27 09:24:52.469230 [thread1] hello from thread1, arg=24, id #1 -2018-08-27 09:24:53.469441 [thread1] hello from thread1, arg=24, id #1 -2018-08-27 09:24:54.469791 [thread1] hello from thread1, arg=24, id #1 -2018-08-27 09:24:54.469757 [thread2] hello from thread2, id #2 -2018-08-27 09:24:55.472021 [thread1] hello from thread1, arg=24, id #1 -2018-08-27 09:24:56.473077 [thread2] hello from thread2, id #2 -2018-08-27 09:24:56.475361 [thread1] hello from thread1, arg=24, id #1 -2018-08-27 09:24:57.476450 [thread1] hello from thread1, arg=24, id #1 -2018-08-27 09:24:58.477224 [thread2] hello from thread2, id #2 -2018-08-27 09:24:58.477225 [thread1] posix thread has returned -2018-08-27 09:24:58.477471 [user] thread complete #1 -2018-08-27 09:25:00.479498 [thread2] hello from thread2, id #2 -2018-08-27 09:25:02.480656 [thread2] hello from thread2, id #2 -2018-08-27 09:25:03.479300 [user] cancelling thread #2 -2018-08-27 09:25:03.479371 [user] waiting for thread #2 -2018-08-27 09:25:03.479435 [user] thread complete #2 -2018-08-27 09:25:05.479844 [user] creating semaphore #0 -2018-08-27 09:25:05.479977 [user] sem id 0 -2018-08-27 09:25:06.481025 [user] thread id 1 -2018-08-27 09:25:06.481079 [thread3] starting posix thread (0xA162220) -2018-08-27 09:25:06.481109 [thread3] waiting for semaphore #0 -2018-08-27 09:25:08.484186 [user] posting semaphore #0 -2018-08-27 09:25:08.484330 [thread3] semaphore wait complete #0 -2018-08-27 09:25:08.484359 [thread3] hello from thread 3 -2018-08-27 09:25:08.484369 [thread3] waiting for semaphore #0 -2018-08-27 09:25:09.485037 [user] posting semaphore #0 -2018-08-27 09:25:09.485127 [thread3] semaphore wait complete #0 -2018-08-27 09:25:09.485140 [thread3] hello from thread 3 -2018-08-27 09:25:09.485149 [thread3] waiting for semaphore #0 +2018-09-16 16:52:38.281053 [user] hello world +2018-09-16 16:52:38.281793 [user] hello world +2018-09-16 16:52:38.281841 [user] thread id 1 +2018-09-16 16:52:38.281850 [thread1] starting posix thread (0xFCD5A10) +2018-09-16 16:52:38.281860 [user] thread id 2 +2018-09-16 16:52:38.281869 [thread2] starting posix thread (0xFCD5B10) +2018-09-16 16:52:38.281876 [user] waiting for thread #1 +2018-09-16 16:52:38.281914 [thread1] hello from thread1 +2018-09-16 16:52:38.281927 [thread2] hello from thread2, id #2 +2018-09-16 16:52:38.281963 [thread1] hello from thread1, id #1 +2018-09-16 16:52:39.286172 [thread1] hello from thread1, id #1 +2018-09-16 16:52:40.285643 [thread2] hello from thread2, id #2 +2018-09-16 16:52:40.286787 [thread1] hello from thread1, id #1 +2018-09-16 16:52:41.286863 [thread1] hello from thread1, id #1 +2018-09-16 16:52:42.286156 [thread2] hello from thread2, id #2 +2018-09-16 16:52:42.287227 [thread1] hello from thread1, id #1 +2018-09-16 16:52:43.287423 [thread1] hello from thread1, id #1 +2018-09-16 16:52:44.289092 [thread1] hello from thread1, id #1 +2018-09-16 16:52:44.289092 [thread2] hello from thread2, id #2 +2018-09-16 16:52:45.289178 [thread1] hello from thread1, id #1 +2018-09-16 16:52:46.292749 [thread1] hello from thread1, id #1 +2018-09-16 16:52:46.292746 [thread2] hello from thread2, id #2 +2018-09-16 16:52:47.297975 [thread1] hello from thread1, id #1 +2018-09-16 16:52:48.297823 [thread2] hello from thread2, id #2 +2018-09-16 16:52:48.299590 [thread1] MATLAB function has returned, thread exiting +2018-09-16 16:52:48.299666 [user] thread complete #1 +2018-09-16 16:52:50.302948 [thread2] hello from thread2, id #2 +2018-09-16 16:52:52.307330 [thread2] hello from thread2, id #2 +2018-09-16 16:52:53.301583 [user] cancelling thread #2 +2018-09-16 16:52:53.301710 [user] waiting for thread #2 +2018-09-16 16:52:53.301815 [user] thread complete #2 +2018-09-16 16:52:55.302909 [user] creating semaphore #0 +2018-09-16 16:52:55.302950 [user] sem id 0 +2018-09-16 16:52:56.307164 [user] thread id 1 +2018-09-16 16:52:56.307204 [thread3] starting posix thread (0xFCD5BD0) +2018-09-16 16:52:56.307233 [thread3] waiting for semaphore #0 +2018-09-16 16:52:58.311708 [user] posting semaphore #0 +2018-09-16 16:52:58.311830 [thread3] semaphore wait complete #0 +2018-09-16 16:52:58.311845 [thread3] hello from thread 3 +2018-09-16 16:52:58.311855 [thread3] waiting for semaphore #0 +2018-09-16 16:52:59.312160 [user] posting semaphore #0 +2018-09-16 16:52:59.312197 [thread3] semaphore wait complete #0 +2018-09-16 16:52:59.312204 [thread3] hello from thread 3 +2018-09-16 16:52:59.312208 [thread3] waiting for semaphore #0 ``` ## Example 2: Web server +The user's main program is quite simple: +```matlab +function user() %#codegen + stl.log('user program starts'); + webserver(8080, 'myserver'); + stl.sleep(60); +end +``` + +Pointing a browser at port 8080 on the host running the program interacts with the MATLAB webserver code which is fairly clearly expressed. ```matlab function myserver() % called on every page request switch (webserver.url()) case '/' - webserver.html('hello world'); % display a simple string - case '/bob' - webserver.html('hello from /bob'); - a = webserver.getarg('a'); % test for argument of the form ?a=X + stl.log('in /') + webserver.html('home here'); + case '/page1' + stl.log('in /page1'); + if webserver.isGET() + stl.log('GET request'); + end + a = webserver.getarg('a'); if ~isempty(a) - stllog('a = %s', cstring(a)); + stl.log('a = %s', cstring(a)); end - case '/alice' + webserver.html('hello from /page1'); + case '/page2' + stl.log('in /page2') vals.a = 1; vals.b = 2; - webserver.template('templates/alice.html', vals); % create a templated response - case '/duck': - webserver.file('duck.jpg', 'image/jpeg'); % return an image + webserver.template('templates/page2.html', vals); + case '/duck' + webserver.file('duck.jpg', 'image/jpeg'); + case '/input' + if webserver.isPOST() + stl.log('POST request'); + foo = webserver.postarg('Foo'); + stl.log('foo = %s', cstring(foo)); + else + stl.log('GET request'); + end + webserver.template('templates/input.html'); + end +end +``` +The switch statement is used to select the code according to the URL given, and other methods provide access to parameters of the HTTP request. +
+ +![page1](https://github.com/petercorke/simple-threads-coder/blob/master/doc/page1.png) + +``` +2018-09-16 15:54:28.635937 [user] user program starts +2018-09-16 15:54:28.636843 [user] web server starting on port 8080 +2018-09-16 15:54:33.170370 [user] web: GET request using HTTP/1.1 for URL /page1 +2018-09-16 15:54:33.170410 [WEB] in /page1 +2018-09-16 15:54:33.170416 [WEB] GET request +2018-09-16 15:54:33.170421 [WEB] a = 7 +2018-09-16 15:54:33.170425 [WEB] web_html: hello from /page1 +``` +Note the arguements `?a=7&b=12` on the end of the URL. These are GET arguments of the form `key=value`, and the method `getarg` provides access to them by key. In this case we get the value of the key `a`. + +Note also, that log messages from the web server function are listed as coming from the `WEB` thread, which is created by `webserver()`. +
+ +![page2](https://github.com/petercorke/simple-threads-coder/blob/master/doc/page2.png) + +``` +2018-09-16 15:39:12.816790 [WEB] web: GET request using HTTP/1.1 for URL /page2 +2018-09-16 15:39:12.816822 [WEB] in /page2 +2018-09-16 15:39:12.816827 [WEB] web_setvalue: a 1 +2018-09-16 15:39:12.816849 [WEB] web_setvalue: b 2 +2018-09-16 15:39:12.816854 [WEB] web_template: templates/page2.html ``` -The template file looks like +The template file is sent to the browser with substitutions. The `page2.html` looks like ```html @@ -207,6 +262,55 @@ The template file looks like ``` and the values of the fields of the struct `vals` are substituted for the corresonding named `TMPL_VAR` tags. +
+ +![duck](https://github.com/petercorke/simple-threads-coder/blob/master/doc/duck.png) + +``` +2018-09-16 15:36:08.881139 [WEB] web: GET request using HTTP/1.1 for URL /duck +2018-09-16 15:36:08.881159 [WEB] web_file: duck.jpg, type image/jpeg +2018-09-16 15:36:08.881208 [WEB] file is 83234 bytes +``` +The local file `duck.png` is sent to the browser as an `image/jpeg` filetype. +
+ +![post](https://github.com/petercorke/simple-threads-coder/blob/master/doc/post.png) + +``` +2018-09-16 16:32:00.035029 [user] web: GET request using HTTP/1.1 for URL /input +2018-09-16 16:32:00.035101 [WEB] input called +2018-09-16 16:32:00.035109 [WEB] GET request +2018-09-16 16:32:00.035118 [WEB] web_template: templates/input.html +2018-09-16 16:32:04.385387 [WEB] web: POST request using HTTP/1.1 for URL /input +2018-09-16 16:32:04.385580 [WEB] web: POST request using HTTP/1.1 for URL /input +2018-09-16 16:32:04.385623 [WEB] POST [Foo] = 27 +2018-09-16 16:32:04.385634 [WEB] POST [button] = Button2 +2018-09-16 16:32:04.385655 [WEB] web: POST request using HTTP/1.1 for URL /input +2018-09-16 16:32:04.385666 [WEB] input called +2018-09-16 16:32:04.385671 [WEB] POST request +2018-09-16 16:32:04.385676 [WEB] foo = 27 +2018-09-16 16:32:04.385681 [WEB] web_template: templates/input.html +``` +This example is rather more complex. The page is requested with a standard GET request and the HTML file `input.html` is returned to the browser +```html + + +

This is a page to test POST

+
+

Enter value of foo: + + + + +

+

+
+ + +``` + The browser displays a form with a text input box and two buttons. When either button (or a newline) is entered, the browser sends the contents of the text box and the button that pushed as `name=value` pairs. The postarg method gets the value of the textbox which has `name=Foo`. The diagnostic messages show that `Button2` was pressed, and this could be tested by accessing the value for the name `button`. + + The POST request must return a value, and in this case it is template file. --- Created using Sublime3 with awesome packages `MarkdownEditing`, `MarkdownPreview` and `Livereload` for WYSIWYG markdown editing. diff --git a/doc/alice.png b/doc/alice.png deleted file mode 100644 index 01ba76c..0000000 Binary files a/doc/alice.png and /dev/null differ diff --git a/doc/duck.png b/doc/duck.png new file mode 100644 index 0000000..22c02fa Binary files /dev/null and b/doc/duck.png differ diff --git a/doc/input.png b/doc/input.png new file mode 100644 index 0000000..61a73e3 Binary files /dev/null and b/doc/input.png differ diff --git a/doc/page1.png b/doc/page1.png new file mode 100644 index 0000000..f37585a Binary files /dev/null and b/doc/page1.png differ diff --git a/doc/page2.png b/doc/page2.png new file mode 100644 index 0000000..678fbd6 Binary files /dev/null and b/doc/page2.png differ diff --git a/doc/post.png b/doc/post.png new file mode 100644 index 0000000..972e452 Binary files /dev/null and b/doc/post.png differ diff --git a/examples/threads/make.m b/examples/threads/make.m index 62242a1..11502e4 100644 --- a/examples/threads/make.m +++ b/examples/threads/make.m @@ -33,10 +33,10 @@ cfg.PostCodeGenCommand = 'postbuild(projectName, buildInfo)'; %{ cfg.InlineThreshold = 0; % tone down the aggressive inlining -codegen user.m thread1.m -args int32(0) thread2.m thread3.m -O disable:inline -config cfg +codegen user.m thread1.m thread2.m thread3.m -O disable:inline -config cfg %} -codegen user.m thread1.m -args z thread2.m thread3.m -config cfg +codegen user.m thread1.m thread2.m thread3.m -config cfg %MAKE = gmake %MAKE_FLAGS = -f $(MAKEFILE) % hw = coder.hardware('Raspberry Pi'); diff --git a/examples/threads/thread1.m b/examples/threads/thread1.m index 9cde05c..61ba1f2 100644 --- a/examples/threads/thread1.m +++ b/examples/threads/thread1.m @@ -1,13 +1,11 @@ -function thread1(arg) %#codegen +function thread1() %#codegen + + stl.log('hello from thread1'); - stllog('hello from thread1'); - stllog('struct.a=%f', arg.a); - stllog('struct.b=%f', arg.b); for i=1:10 - %stllog('hello from thread1, arg=%d, id #%d', arg, self()); - stllog('hello from thread1, id #%d', self()); - sleep(1) + stl.log('hello from thread1, id #%d', stl.self()); + stl.sleep(1) end end diff --git a/examples/threads/thread2.m b/examples/threads/thread2.m index de652fc..05b664c 100644 --- a/examples/threads/thread2.m +++ b/examples/threads/thread2.m @@ -1,6 +1,6 @@ function thread2() %#codegen for i=1:20 - stllog('hello from thread2, id #%d', self()); - sleep(2) + stl.log('hello from thread2, id #%d', stl.self()); + stl.sleep(2) end end diff --git a/examples/threads/thread3.m b/examples/threads/thread3.m index 6cdae3c..f9c068f 100644 --- a/examples/threads/thread3.m +++ b/examples/threads/thread3.m @@ -1,7 +1,7 @@ function thread3() %#codegen while true - semwait(0); - stllog('hello from thread 3'); + stl.semaphore_wait(0); + stl.log('hello from thread 3'); end end diff --git a/examples/threads/user.m b/examples/threads/user.m index 9a12fa5..2525ff3 100644 --- a/examples/threads/user.m +++ b/examples/threads/user.m @@ -4,54 +4,49 @@ fprintf(2, 'hello world\n'); % we have access to command line arguments - fprintf(2, 'got %d arguments\n', argc() ); - for i=0:argc()-1 - fprintf(2, ' arg %d: %s\n', i, argv(i)); + fprintf(2, 'got %d arguments\n', stl.argc() ); + for i=0:stl.argc()-1 + fprintf(2, ' arg %d: %s\n', i, stl.argv(i)); end % we can send timestamped messages to the log % - no need to put a new line on the end - stllog('hello world'); + stl.log('hello world'); % note that if we send a string argument we need to convert it to a % C string - stllog('%s', cstring('hello world')); + stl.log('%s', cstring('hello world')); % now we can launch a couple of threads, see thread1.m and thread2.m % launching returns a thread id, a small integer - z.a = 1 - z.b = 2 + t1 = stl.launch('thread1') + stl.log('thread id %d', t1) + t2 = stl.launch('thread2') + stl.log('thread id %d', t2) - coder.cstructname(z, 'arg_t') + stl.join(t1); % wait for thread 1 to finish + stl.sleep(5); + stl.cancel(t2); % kill thread 2 and wait for it + stl.join(t2) - t1 = launch('thread1', z) % pass a value to this thread - stllog('thread id %d', t1) - t2 = launch('thread2') - stllog('thread id %d', t2) - - join(t1); % wait for thread 1 to finish - sleep(5); - cancel(t2); % kill thread 2 and wait for it - join(t2) - - sleep(2) + stl.sleep(2) % create a semaphore - s1 = newsemaphore('sem1'); - stllog('sem id %d', s1); - sleep(1) + s1 = stl.semaphore('sem1'); + stl.log('sem id %d', s1); + stl.sleep(1) % launch a new thread, see thread3.m % it just waits for the semaphore, then prints a message - t3 = launch('thread3', 42); - stllog('thread id %d', t3); - - sleep(2); - sempost(0); % wake up thread 3 - sleep(1); - sempost(0); % wake up thread 3 - sleep(2); + t3 = stl.launch('thread3', 42); + stl.log('thread id %d', t3); + + stl.sleep(2); + stl.semaphore_post(0); % wake up thread 3 + stl.sleep(1); + stl.semaphore_post(0); % wake up thread 3 + stl.sleep(2); % done, exiting will tear down all the threads diff --git a/examples/timer/Makefile b/examples/timer/Makefile new file mode 100644 index 0000000..9f962b0 --- /dev/null +++ b/examples/timer/Makefile @@ -0,0 +1,9 @@ +# copy this into the codegen/exe/user folder on the target machine +# then run it, it will build the binary in this folder +vpath %c ../../../stl +OBJ = thread1.o thread2.o thread3.o user.o user_initialize.o user_terminate.o main.o stl.o +CFLAGS += -I . -I ../../../stl +LIBS = -ldl -lpthread -lrt + +user: $(OBJ) + $(CC) -o main -fPIC $(OBJ) $(LIBS) diff --git a/examples/timer/make.m b/examples/timer/make.m new file mode 100644 index 0000000..ac76c45 --- /dev/null +++ b/examples/timer/make.m @@ -0,0 +1,43 @@ +% make.m + +cfg = coder.config('exe') +cfg.TargetLang = 'C'; +cfg.MultiInstanceCode = true; + +cfg.GenerateReport = true; +%cfg.LaunchReport = true; + +% build options +%cfg.GenerateExampleMain = 'DoNotGenerate'; +cfg.GenCodeOnly = true; + +cfg.GenerateComments = true; +cfg.MATLABSourceComments = true; % lots of comments +cfg.PreserveVariableNames = 'UserNames'; + +% want to include some files from this directory, but this doesnt work?? +cfg.CustomSource = 'main.c stl.c' +cfg.CustomInclude = '../../stl'; + +cfg.BuildConfiguration = 'Faster Builds'; +%cfg.BuildConfiguration = 'Debug'; + +% would be nice if could set make -j to get some parallel building +% happening. + +%for Raspberry Pi +% hw = coder.hardware('Raspberry Pi'); +% cfg.Hardware = hw; +% cfg.GenCodeOnly = true; + +cfg.PostCodeGenCommand = 'postbuild(projectName, buildInfo)'; +%{ +cfg.InlineThreshold = 0; % tone down the aggressive inlining +codegen user.m thread1.m -args int32(0) thread2.m thread3.m -O disable:inline -config cfg +%} + +codegen user.m thread1.m -config cfg +%MAKE = gmake +%MAKE_FLAGS = -f $(MAKEFILE) +% hw = coder.hardware('Raspberry Pi'); +% cfg.Hardware = hw; diff --git a/examples/timer/thread1.m b/examples/timer/thread1.m new file mode 100644 index 0000000..1e15cfd --- /dev/null +++ b/examples/timer/thread1.m @@ -0,0 +1,11 @@ +function thread1() %#codegen + + stllog('hello from thread1'); + + for i=1:100 + stl.semwait(0) + ii = int32(i) + stl.log('wakeup %d', ii); + end +end + diff --git a/examples/timer/user.m b/examples/timer/user.m new file mode 100644 index 0000000..cd98c08 --- /dev/null +++ b/examples/timer/user.m @@ -0,0 +1,22 @@ +function user %#codegen + + + +% z.a = 1 +% z.b = 2 +% coder.cstructname(z, 'arg_t') + + s1 = stl.semaphore('sem1'); + stllog('sem id ', s1); + + t1 = launch('thread1', 0) % pass a value to this thread + stllog('thread id %d', t1) + + timer = stl.timer('timer1', 2.0, s1); + + join(t1); % wait for thread 1 to finish + + % done, exiting will tear down all the threads + +end + diff --git a/examples/webserver/myserver.m b/examples/webserver/myserver.m index 180aaee..e2193d7 100644 --- a/examples/webserver/myserver.m +++ b/examples/webserver/myserver.m @@ -1,22 +1,34 @@ -function myserver() % called from C +function myserver() % called on every page request switch (webserver.url()) case '/' - stllog('in /') + stl.log('in /') webserver.html('home here'); - case '/bob' - %webserver.template('templates/home.html', values); - stllog('in /bob'); + case '/page1' + stl.log('in /page1'); + if webserver.isGET() + stl.log('GET request'); + end a = webserver.getarg('a'); if ~isempty(a) - stllog('a = %s', cstring(a)); + stl.log('a = %s', cstring(a)); end - webserver.html('hello from /bob'); - case '/alice' - stllog('in /alice') + webserver.html('hello from /page1'); + case '/page2' + stl.log('in /page2') vals.a = 1; vals.b = 2; - webserver.template('templates/alice.html', vals); + webserver.template('templates/page2.html', vals); case '/duck' webserver.file('duck.jpg', 'image/jpeg'); + case '/input' + if webserver.isPOST() + stl.log('POST request'); + foo = webserver.postarg('Foo'); + stl.log('foo = %s', cstring(foo)); + else + stl.log('GET request'); + end + webserver.template('templates/input.html'); + end end \ No newline at end of file diff --git a/examples/webserver/templates/input.html b/examples/webserver/templates/input.html new file mode 100644 index 0000000..d0a6e38 --- /dev/null +++ b/examples/webserver/templates/input.html @@ -0,0 +1,14 @@ + + +

This is a page to test POST

+
+

Enter value of foo: + + + + +

+

+
+ + diff --git a/examples/webserver/templates/alice.html b/examples/webserver/templates/page2.html similarity index 100% rename from examples/webserver/templates/alice.html rename to examples/webserver/templates/page2.html diff --git a/examples/webserver/user.m b/examples/webserver/user.m index 46760b0..8b04bc7 100644 --- a/examples/webserver/user.m +++ b/examples/webserver/user.m @@ -1,8 +1,8 @@ function user() %#codegen - stllog('user program starts'); + stl.log('user program starts'); webserver(8080, 'myserver'); - sleep(60); + stl.sleep(60); end \ No newline at end of file diff --git a/stl/argc.m b/stl/argc.m deleted file mode 100644 index 92a6afa..0000000 --- a/stl/argc.m +++ /dev/null @@ -1,6 +0,0 @@ -function ac = argc() - coder.cinclude('stl.h'); - - ac = int32(0); - ac = coder.ceval('stl_argc'); % evaluate the C function -end diff --git a/stl/argv.m b/stl/argv.m deleted file mode 100644 index dcd267e..0000000 --- a/stl/argv.m +++ /dev/null @@ -1,20 +0,0 @@ -function s = argv(a) - coder.cinclude('stl.h'); - - s = ''; - coder.varsize('s'); - - BUFSIZ = 256; - buf = char(zeros(1,BUFSIZ)); % create a buffer to write into, all nulls - - coder.ceval('stl_argv', a, coder.wref(buf), BUFSIZ); % evaluate the C function - - % find the end of the string, where the first unwritten null is - for i=1:BUFSIZ-1 - if buf(i) == 0 - % found a null, return variable length array up to here - s = buf(1:i-1); - return; - end - end -end diff --git a/stl/cancel.m b/stl/cancel.m deleted file mode 100644 index f4b489a..0000000 --- a/stl/cancel.m +++ /dev/null @@ -1,5 +0,0 @@ -function cancel(id) - coder.cinclude('stl.h'); - - coder.ceval('stl_thread_cancel', id ); % evaluate the C function -end diff --git a/stl/httpd.c b/stl/httpd.c index 0c32246..bf64db7 100644 --- a/stl/httpd.c +++ b/stl/httpd.c @@ -1,5 +1,11 @@ +/* + * Simple webserver for MATLAB Coder + * Peter Corke August 2018 + */ + #include #include +//#include #include #include #include @@ -27,56 +33,203 @@ static void send_data(void *s, int len, char *type); static int print_key (void *cls, enum MHD_ValueKind kind, const char *key, const char *value); -// local copies of the request parameters -static TMPL_varlist *web_varlist; -static struct MHD_Connection *req_connection; -static int req_response_status; -static char *req_url; -static char *req_method; +// variables that hold state during the request +static struct MHD_Connection *req_connection; +static int req_response_status; +static char *req_url; +static char *req_method; +static TMPL_varlist *req_varlist = NULL; // list of template variables for this request +struct MHD_PostProcessor *pp; +// local variables int web_debug_flag = 1; +int page_request_once = 0; +int page_request_responses = 0; + + +// forward defines +static void postvar_add(char *key, char *value); +static char *postvar_find(char *key); +static void postvar_free(); +void *malloc(); // stdlib.h clashes with microhttpd.h +void *calloc(size_t count, size_t size); +void free(void *); + +int +post_data_iterator(void *cls, enum MHD_ValueKind kind, + const char *key, + const char *filename, const char *type, const char *encoding, + const char *data, uint64_t off, size_t size) +{ + // Called on every POST variable uploaded + + // make null terminated heap copy of the value + char *value = (char *)calloc(size+1, 1); + strncpy(value, data, size); + + // add to the list of POST variables + postvar_add(stl_stralloc(key), value); + stl_log("POST [%s] = %s", key, value); + + return MHD_YES; +} + + + +//------------------- handle the page request static int page_request (void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { + WEB_DEBUG("web: %s request using %s for URL %s ", method, version, url); - /* -MHD_get_connection_values (connection, MHD_HEADER_KIND, print_key, NULL); -MHD_get_connection_values (connection, MHD_GET_ARGUMENT_KIND, print_key, NULL); - */ - WEB_DEBUG("web: %s request for URL %s using %s", method, url, version); + // on first invocation by this thread, add it to the local thread table + if (page_request_once == 0) { + page_request_once = 1; + stl_thread_add("WEB"); + } - // save some of the parameters for access by MATLAB calls + // save some of the parameters for access by MATLAB calls req_connection = connection; req_url = (char *)url; req_method = (char *)method; + + if (strcmp(method, MHD_HTTP_METHOD_POST) == 0) { + + pp = *con_cls; + if (pp == NULL) { + // new POST request + pp = MHD_create_post_processor(connection, 32*1024, post_data_iterator, pp); + *con_cls = (void *)pp; + return MHD_YES; + } + + if (*upload_data_size) { + // deal with POST data, feed the post processor + MHD_post_process(pp, upload_data, *upload_data_size); + *upload_data_size = 0; // flag that we've dealt with the data + return MHD_YES; + } + } - // free up the old list somewhere TMPL_free_varlist(varlist) - web_varlist = NULL; // set the template list to empty + // set the template varlist to empty + req_varlist = NULL; // set the return status to fail, it will be set by any of the callbacks req_response_status = MHD_NO; // call the user's MATLAB code + page_request_responses = 0; + request_matlab_callback(); + + // free up the template varlist + if (req_varlist) + TMPL_free_varlist(req_varlist); + + // free up the POST var list + postvar_free(); + + if (strcmp(method, MHD_HTTP_METHOD_GET) == 0) { + // GET // check whether user code responded + if (page_request_responses == 0) + web_error(404, "URL not found"); + } + - // return the status MHD_YES=1, MHD_NO=0 - stl_log("return status %d", req_response_status); - return req_response_status; + // return the status MHD_YES=1, MHD_NO=0 + if (req_response_status == MHD_NO) + stl_log("error creating a response"); + return req_response_status; } +//------------------- information about the response /** - * Send string to the web browser + * Return the URL for this request */ void -web_html(char *html) +web_url(char *buf, int buflen) +{ + strncpy(buf, req_url, buflen); +} + +int +web_isPOST() +{ + return strcmp(req_method, MHD_HTTP_METHOD_POST) == 0; +} + +int32_t +web_getarg(char *buf, int len, char *name) +{ + char *value = (char *)MHD_lookup_connection_value(req_connection, MHD_GET_ARGUMENT_KIND, name); + + if (value) { + strncpy(buf, value, len); + buf[len-1] = 0; // for safety with long strings + return 1; // key found + } else + return 0; +} + +int32_t +web_postarg(char *buf, int len, char *name) +{ + char *value = postvar_find(name); + + if (value) { + strncpy(buf, value, len); + return 1; // key found + } else + return 0; +} + +int +web_reqheader(char *buf, int len, char *name) +{ + char *value = (char *)MHD_lookup_connection_value(req_connection, MHD_HEADER_KIND, name); + + if (value) { + strncpy(buf, value, len); + buf[len-1] = 0; // for safety with long strings + return 1; // key found + } else + return 0; +} + +//------------------- create a response to the request +void web_error(int errcode, char *errmsg) { + WEB_DEBUG("web_error: %d, %s", errcode, errmsg); + page_request_responses++; // indicate a reponse to the request + struct MHD_Response *response; + response = MHD_create_response_from_buffer(strlen(errmsg), errmsg, MHD_RESPMEM_MUST_COPY); + + MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html"); + req_response_status = MHD_queue_response(req_connection, errcode, response); + MHD_destroy_response(response); +} + +void web_show_request_header() +{ + MHD_get_connection_values (req_connection, MHD_HEADER_KIND, print_key, "REQUEST"); + MHD_get_connection_values (req_connection, MHD_GET_ARGUMENT_KIND, print_key, "GET"); + MHD_get_connection_values (req_connection, MHD_POSTDATA_KIND, print_key, "POST"); +} + +/** + * Send string to the web browser + */ +void +web_html(char *html) +{ WEB_DEBUG("web_html: %s", html); + page_request_responses++; // indicate a reponse to the request send_data(html, strlen(html), "text/html"); } @@ -86,30 +239,24 @@ web_html(char *html) */ void web_template(char *filename) { + WEB_DEBUG("web_template: %s", filename); + page_request_responses++; // indicate a reponse to the request + + // process the template into a memory based file pointer char buffer[BUFSIZ]; FILE *html = fmemopen(buffer, BUFSIZ, "w"); - WEB_DEBUG("web_template: %s", filename); - TMPL_write(filename, 0, 0, web_varlist, html, stderr); + TMPL_write(filename, 0, 0, req_varlist, html, stderr); fclose(html); send_data(buffer, strlen(buffer), "text/html"); } -/** - * Return the URL for this request - */ -void -web_url(char *buf, int buflen) -{ - WEB_DEBUG("web_url: %s", req_url); - strncpy(buf, req_url, buflen); -} - void web_file(char *filename, char *type) { WEB_DEBUG("web_file: %s, type %s", filename, type); + page_request_responses++; // indicate a reponse to the request struct MHD_Response *response; int fd; @@ -123,7 +270,7 @@ web_file(char *filename, char *type) if (ret != 0) stl_error("web_file: couldn't stat file %s", filename); - stl_log("file is %ld bytes", (uint64_t) statbuf.st_size); + stl_log("file is %llu bytes", statbuf.st_size); response = MHD_create_response_from_fd(statbuf.st_size, fd); MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, type); MHD_add_response_header(response, MHD_HTTP_HEADER_CONNECTION, "close"); @@ -135,54 +282,12 @@ void web_data(void *data, int len, char *type) { WEB_DEBUG("web_data: %d bytes, type %s", len, type); + page_request_responses++; // indicate a reponse to the request send_data(data, len, type); } -int -web_ispost() -{ - return strcmp(req_method, MHD_HTTP_METHOD_POST); -} - -int32_t -web_getarg(char *buf, int len, char *name) -{ - char *value = (char *)MHD_lookup_connection_value(req_connection, MHD_GET_ARGUMENT_KIND, name); - - if (value) { - strncpy(buf, value, len); - buf[len-1] = 0; // for safety with long strings - return 1; // key found - } else - return 0; -} - -int32_t -web_postarg(char *buf, int len, char *name) -{ - char *value = (char *)MHD_lookup_connection_value(req_connection, MHD_POSTDATA_KIND, name); - - if (value) { - strncpy(buf, value, len); - buf[len-1] = 0; // for safety with long strings - return 1; // key found - } else - return 0; -} - -int -web_reqheader(char *buf, int len, char *name) -{ - char *value = (char *)MHD_lookup_connection_value(req_connection, MHD_HEADER_KIND, name); - - if (value) { - strncpy(buf, value, len); - buf[len-1] = 0; // for safety with long strings - return 1; // key found - } else - return 0; -} +//------------------- support /** * Set a value for the template processor @@ -190,21 +295,29 @@ web_reqheader(char *buf, int len, char *name) void web_setvalue(char *name, char *value) { WEB_DEBUG("web_setvalue: %s %s", name, value); - web_varlist = TMPL_add_var(web_varlist, stl_stralloc(name), stl_stralloc(value), NULL); + + // this function copies name/value to the heap + req_varlist = TMPL_add_var(req_varlist, name, value, NULL); } void -web_start(int32_t port, char *callback) +web_start(int32_t port, char *callback, void *arg) { if (daemon) stl_error("web server already launched"); request_matlab_callback = stl_get_functionptr(callback); if (request_matlab_callback == NULL) - stl_error("thread named [%s] not found", callback); + stl_error("MATLAB entrypoint named [%s] not found", callback); - daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, port, NULL, NULL, - &page_request, NULL, MHD_OPTION_END); + daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, port, + NULL, NULL, + &page_request, arg, + MHD_OPTION_END); + + // this starts a POSIX thread but its handle is very well buried + // its name will be MHD-single but this is not gettable under MacOS + if (daemon == NULL) stl_error("web server failed to launch: %s", strerror(errno)); @@ -222,12 +335,70 @@ send_data(void *s, int len, char *type) MHD_destroy_response(response); } +void +web_debug(int32_t debug) +{ + web_debug_flag = debug; +} + static int print_key (void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { - (void)cls; /* Unused. Silent compiler warning. */ - (void)kind; /* Unused. Silent compiler warning. */ - printf ("%s: %s\n", key, value); + stl_log ("%s: %s=%s", (char *)cls, key, value); return MHD_YES; +} + +// manage list of POST variables +typedef struct _postvars { + char *key; + char *value; + struct _postvars *next; +} postvar; + +static postvar *pvhead = NULL; + +void +postvar_add(char *key, char *value) +{ + // expect both args to on the heap + + // create an entry for the list of POST vars + postvar *pv = (postvar *) malloc(sizeof(postvar)); + pv->key = key; + pv->value = value; + + // insert at head of list + pv->next = pvhead; + pvhead = pv; +} + +char * +postvar_find(char *key) +{ + postvar *pv = pvhead; + + while (pv) { + if (strcmp(pv->key, key) == 0) { + // found it + return pv->value; + } + pv = pv->next; + } + return NULL; +} + +void +postvar_free() +{ + postvar *pv = pvhead; + + while (pv) { + free(pv->key); + free(pv->value); + postvar *next = pv->next; + free(pv); + pv = next; + } + pvhead = NULL; } \ No newline at end of file diff --git a/stl/httpd.h b/stl/httpd.h index f4e660c..315c46d 100644 --- a/stl/httpd.h +++ b/stl/httpd.h @@ -1,12 +1,18 @@ +/* + * Simple webserver for MATLAB Coder + * Peter Corke August 2018 + */ #ifndef __httpd_h_ #define __httpd_h_ // C functions in httpd.c which are wrapped by webserver.m -void web_start(int32_t port, char *callback); +void web_start(int32_t port, char *callback, void *arg); +void web_debug(int32_t debug); void web_url(char *buf, int len); -int web_ispost(); +int web_isPOST(); +void web_error(int errcode, char *errmsg); void web_html(char *html); void web_setvalue(char *name, char *value); void web_template(char *filename); diff --git a/stl/join.m b/stl/join.m deleted file mode 100644 index b703c2c..0000000 --- a/stl/join.m +++ /dev/null @@ -1,5 +0,0 @@ -function join(id) - coder.cinclude('stl.h'); - - coder.ceval('stl_thread_join', id ); % evaluate the C function -end diff --git a/stl/launch.m b/stl/launch.m deleted file mode 100644 index e1ad491..0000000 --- a/stl/launch.m +++ /dev/null @@ -1,9 +0,0 @@ -function tid = launch(name, arg) - coder.cinclude('stl.h'); - - if nargin < 2 - arg = 0; - end - tid = int32(0); - tid = coder.ceval('stl_thread_create', cstring(name), coder.ref(arg), int32(0)); % evaluate the C function -end diff --git a/stl/main.c b/stl/main.c index dce2321..b76e8cd 100644 --- a/stl/main.c +++ b/stl/main.c @@ -12,15 +12,34 @@ #include "user_types.h" #include "stl.h" +#ifdef typedef_userStackData +userStackData SD; +#endif +#ifdef typedef_userPersistentData +userPersistentData pd; +#endif + int main(int argc, char **argv) { // initialize the thread library stl_initialize(argc, argv); - // call the user's function + // initialize stack data required for some MATLAB functions +#ifdef typedef_userStackData + #ifdef typedef_userPersistentData + SD.pd = &pd; + #endif + user_initialize(&SD); +#else user_initialize(); - user(); +#endif + + // call the user's function + user(); + // user(&SD); use this if user has a stack pointer argument + + user_terminate(); return 0; diff --git a/stl/mutex.m b/stl/mutex.m deleted file mode 100644 index ee08115..0000000 --- a/stl/mutex.m +++ /dev/null @@ -1,3 +0,0 @@ -function mutex(name) - coder.ceval('stl_launch', [name 0]); % evaluate the C function -end diff --git a/stl/newsemaphore.m b/stl/newsemaphore.m deleted file mode 100644 index b86651b..0000000 --- a/stl/newsemaphore.m +++ /dev/null @@ -1,6 +0,0 @@ -function sid = semaphore(name) - coder.cinclude('stl.h'); - - sid = int32(0); - sid = coder.ceval('stl_sem_create', cstring(name)); % evaluate the C function -end diff --git a/stl/postbuild.m b/stl/postbuild.m index 4c578a9..aa08361 100644 --- a/stl/postbuild.m +++ b/stl/postbuild.m @@ -5,6 +5,6 @@ function postbuild(projectName, buildInfo) 'fileName', [projectName '.zip'], ... 'packType', 'hierarchical', ... 'nestedZipFiles', false ... - } + }; packNGo(buildInfo, options); end diff --git a/stl/self.m b/stl/self.m deleted file mode 100644 index 2a5161a..0000000 --- a/stl/self.m +++ /dev/null @@ -1,6 +0,0 @@ -function id = self() - coder.cinclude('stl.h'); - - id = int32(0); - id = coder.ceval('stl_thread_self'); % evaluate the C function -end diff --git a/stl/sempost.m b/stl/sempost.m deleted file mode 100644 index c4e8f79..0000000 --- a/stl/sempost.m +++ /dev/null @@ -1,5 +0,0 @@ -function sempost(id) - coder.cinclude('stl.h'); - - coder.ceval('stl_sem_post', id); % evaluate the C function -end diff --git a/stl/semwait.m b/stl/semwait.m deleted file mode 100644 index 4886050..0000000 --- a/stl/semwait.m +++ /dev/null @@ -1,8 +0,0 @@ -function semwait(id, wait) - coder.cinclude('stl.h'); - - if nargin < 2 - wait = int32(0); - end - coder.ceval('stl_sem_wait', id, wait); % evaluate the C function -end diff --git a/stl/sleep.m b/stl/sleep.m deleted file mode 100644 index eeebc0d..0000000 --- a/stl/sleep.m +++ /dev/null @@ -1,6 +0,0 @@ - -function sleep(t) - coder.cinclude('stl.h'); - coder.ceval('stl_sleep', t); % evaluate the C function -end - diff --git a/stl/stl.c b/stl/stl.c index af29aef..d75f7a2 100644 --- a/stl/stl.c +++ b/stl/stl.c @@ -14,17 +14,21 @@ #include #ifdef __linux__ -#define __USE_GNU + #define __USE_GNU #endif #include #include #ifdef __linux__ -#include -#include + #include + #include #endif #include -#include "user_types.h" +#ifdef __APPLE__ + #include +#endif + +#include "user_types.h" #include "stl.h" // parameters @@ -53,8 +57,9 @@ typedef struct _thread { pthread_t pthread; // the POSIX thread handle char *name; int busy; - void * (*f)(void *); // pointer to thread function entry point + void *f; // pointer to thread function entry point void *arg; + int hasstackdata; } thread; typedef struct _semaphore { @@ -113,10 +118,7 @@ stl_initialize(int argc, char **argv) stl_error("initialize: mutex create failed %s", strerror(status)); // allocate a dummy thread list entry for the main thread - threadlist[0].busy = 1; - threadlist[0].name = "user"; - threadlist[0].f = NULL; // no pointer to function entry - threadlist[0].pthread = (pthread_t)NULL; // it has no thread handle + stl_thread_add("user"); } void @@ -139,6 +141,11 @@ stl_argv(int32_t a, char *arg, int32_t len) strncpy(arg, stl_cmdline_argv[a], len); } +void +stl_require(void *v) +{ +} + void stl_sleep(double t) { @@ -153,8 +160,8 @@ stl_sleep(double t) } -int -stl_thread_create(char *func, void *arg, char *name) +int32_t +stl_thread_create(char *func, void *arg, int hasstackdata) { pthread_attr_t attr; void * (*f)(void *); @@ -165,7 +172,7 @@ stl_thread_create(char *func, void *arg, char *name) // map function name to a pointer f = (void *(*)(void *)) stl_get_functionptr(func); if (f == NULL) - stl_error("thread function named [%s] not found", func); + stl_error("thread_create: MATLAB entrypoint named [%s] not found", func); // find an empty slot LIST_LOCK @@ -179,14 +186,12 @@ stl_thread_create(char *func, void *arg, char *name) } LIST_UNLOCK if (tp == NULL) - stl_error("too many threads, increase NTHREADS (currently %d)", NTHREADS); + stl_error("thread_create: too many threads, increase NTHREADS (currently %d)", NTHREADS); - if (name) - tp->name = stl_stralloc(name); - else - tp->name = stl_stralloc(func); + tp->name = stl_stralloc(func); tp->f = f; - tp->arg = (void *)arg; + tp->arg = arg; + tp->hasstackdata = hasstackdata; // set attributes pthread_attr_init(&attr); @@ -194,20 +199,79 @@ stl_thread_create(char *func, void *arg, char *name) // check result status = pthread_create(&(tp->pthread), &attr, (void *(*)(void *))stl_thread_wrapper, tp); if (status) - stl_error("create: failed %s", strerror(status)); + stl_error("thread_create: create <%s> failed %s", tp->name, strerror(status)); return slot; } +int +stl_thread_add(char *name) +{ + int slot; + thread *p, *tp = NULL; + + // find an empty slot + LIST_LOCK + for (p=threadlist, slot=0; slotbusy == 0) { + tp = p; + tp->busy++; // mark it busy + break; + } + } + LIST_UNLOCK + if (tp == NULL) + stl_error("thread_add: too many threads, increase NTHREADS (currently %d)", NTHREADS); + + tp->name = stl_stralloc(name); + tp->pthread = pthread_self(); + tp->f = NULL; + + return slot; +} + + static void stl_thread_wrapper( thread *tp) { - STL_DEBUG("starting posix thread <%s> (0x%X)", tp->name, (uint32_t)tp->f); + char *info; + if (tp->hasstackdata) + info = "[has stack data]"; + else + info = ""; + STL_DEBUG("starting posix thread <%s> (0x%X) %s", tp->name, (uint32_t)tp->f, info); + + // inform kernel about the thread's name + // under linux can see this with ps -o cat /proc/$PID/task/$TID/comm + // settable for MacOS but seemingly not visible, but it does show up in core dumps +#if defined(__linux__) || defined(__unix__) + pthread_setname_np(tp->pthread, tp->name); +#endif +#ifdef __APPLE__ + pthread_setname_np(tp->name); +#endif + + +#ifdef typedef_userStackData + extern userStackData SD; // invoke the user's compiled MATLAB code - tp->f(tp->arg); + // if the function has stack data, need to pass that as first argument + if (tp->hasstackdata) { + void (*f)(void *, void *) = (void (*)(void *, void*)) tp->f; // pointer to thread function entry point - STL_DEBUG("posix thread <%s> has returned", tp->name); + f(&SD, tp->arg); + } + else { + void (*f)(void *) = (void (*)(void *))tp->f; // pointer to thread function entry point + f(tp->arg); + } +#else + void (*f)(void *) = (void (*)(void *))tp->f; // pointer to thread function entry point + f(tp->arg); +#endif + + STL_DEBUG("MATLAB function <%s> has returned, thread exiting", tp->name); tp->busy = 0; // free the slot in thread table } @@ -215,9 +279,9 @@ stl_thread_wrapper( thread *tp) char * stl_thread_name(int32_t slot) { - if (slot < 0) + if (slot < 0) slot = stl_thread_self(); - + return threadlist[slot].name; } @@ -229,10 +293,10 @@ stl_thread_cancel(int32_t slot) STL_DEBUG("cancelling thread #%d <%s>", slot, threadlist[slot].name); if (threadlist[slot].busy == 0) - stl_error("cancel: thread %d not busy", slot+1); + stl_error("thread_cancel: thread %d not allocated", slot+1); status = pthread_cancel(threadlist[slot].pthread); if (status) - stl_error("cancel: failed %s", strerror(status)); + stl_error("thread_cancel: <%s> failed %s", threadlist[slot].name, strerror(status)); } @@ -245,11 +309,11 @@ stl_thread_join(int32_t slot) STL_DEBUG("waiting for thread #%d <%s>", slot, threadlist[slot].name); if (threadlist[slot].busy == 0) - stl_error("join: thread %d not busy", slot+1); + stl_error("thread_join: thread %d not allocated", slot+1); status = pthread_join(threadlist[slot].pthread, (void **)&exitval); if (status) - stl_error("join: failed %s", strerror(status)); + stl_error("thread_join: <%s> failed %s", threadlist[slot].name, strerror(status)); STL_DEBUG("thread complete #%d <%s>", slot, threadlist[slot].name); return (int32_t) exitval; @@ -288,11 +352,11 @@ stl_sem_create(char *name) } LIST_UNLOCK if (sp == NULL) - stl_error("too many semaphores, increase NSEMAPHORES (currently %d)", NSEMAPHORES); + stl_error("sem_create: too many semaphores, increase NSEMAPHORES (currently %d)", NSEMAPHORES); sem = sem_open(name, O_CREAT, 0700, 0); if (sem == SEM_FAILED) - stl_error("sem: failed %s", strerror(errno)); + stl_error("sem_create: <%s> failed %s", name, strerror(errno)); sp->sem = sem; sp->name = stl_stralloc(name); @@ -309,47 +373,57 @@ stl_sem_post(int32_t slot) STL_DEBUG("posting semaphore #%d <%s>", slot, semlist[slot].name); if (semlist[slot].busy == 0) - stl_error("join: sem %d not allocatedq", slot); + stl_error("sem_post: sem %d not allocated", slot); status = sem_post(semlist[slot].sem); if (status) - stl_error("join: failed %s", strerror(errno)); + stl_error("sem_post: <%s> failed %s", semlist[slot].name, strerror(errno)); } int -stl_sem_wait(int32_t slot, int32_t nowait) +stl_sem_wait(int32_t slot) { int status; if (semlist[slot].busy == 0) - stl_error("sem wait: sem %d not allocated", slot); - - if (nowait) { - // non-blocking wait - status = sem_trywait(semlist[slot].sem); + stl_error("sem_wait: sem %d not allocated", slot); - if (status == EAGAIN) { - STL_DEBUG("polling semaphore - BLOCKED #%d <%s>", slot, semlist[slot].name); - return 0; // still locked, return false - } else if (status == 0) { - STL_DEBUG("polling semaphore - FREE #%d <%s>", slot, semlist[slot].name); - return 1; // not locked, return true - } - } - else { - // blocking wait on semaphore - STL_DEBUG("waiting for semaphore #%d <%s>", slot, semlist[slot].name); - status = sem_wait(semlist[slot].sem); - } + // blocking wait on semaphore + STL_DEBUG("waiting for semaphore #%d <%s>", slot, semlist[slot].name); + status = sem_wait(semlist[slot].sem); if (status) - stl_error("sem: wait/trywait failed %s", strerror(errno)); + stl_error("sem_wait: <%s> failed %s", semlist[slot].name, strerror(errno)); STL_DEBUG("semaphore wait complete #%d", slot); return 1; // semaphore is ours, return true } +int +stl_sem_wait_noblock(int32_t slot) +{ + int status; + + if (semlist[slot].busy == 0) + stl_error("sem_wait_noblock: sem %d not allocated", slot); + + // non-blocking wait + status = sem_trywait(semlist[slot].sem); + + switch (status) { + case 0: + STL_DEBUG("polling semaphore - FREE #%d <%s>", slot, semlist[slot].name); + return 1; // not locked, it's ours, return true + case EAGAIN: + STL_DEBUG("polling semaphore - BLOCKED #%d <%s>", slot, semlist[slot].name); + return 0; // still locked, return false + default: + stl_error("sem_wait_noblock: <%s> failed %s", semlist[slot].name, strerror(errno)); + } + return 0; // return false +} + int32_t stl_mutex_create(char *name) { @@ -370,14 +444,16 @@ stl_mutex_create(char *name) } LIST_UNLOCK if (mp == NULL) - stl_error("too many mutexes, increase NMUTEXS (currently %d)", NMUTEXS); + stl_error("mutex_create: too many mutexes, increase NMUTEXS (currently %d)", NMUTEXS); + + mp->name = name; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); status = pthread_mutex_init(&mp->pmutex, &attr); if (status) - stl_error("mutex create: failed %s", strerror(status)); + stl_error("mutex_create: <%s> failed %s", mutexlist[slot].name, strerror(status)); STL_DEBUG("create mutex #%d <%s>", slot, name); @@ -385,52 +461,64 @@ stl_mutex_create(char *name) } int32_t -stl_mutex_lock(int32_t slot, int32_t nowait) +stl_mutex_lock(int32_t slot) { int status; if (mutexlist[slot].busy == 0) - stl_error("lock: mutex %d not allocatedq", slot); + stl_error("mutex_lock: mutex %d not allocatedq", slot); - if (nowait) { - // non-blocking wait - status = pthread_mutex_trylock(&mutexlist[slot].pmutex); - if (status == EBUSY) { - STL_DEBUG("test mutex - LOCKED #%d <%s>", slot, semlist[slot].name); - return 0; // still locked, return false - } else if (status == 0) { - STL_DEBUG("test mutex - UNLOCKED #%d <%s>", slot, semlist[slot].name); - return 1; // unlocked, return true - } - } - else { - // blocking wait on mutex - STL_DEBUG("attempting lock on mutex #%d <%s>", slot, mutexlist[slot].name); - status = pthread_mutex_lock(&mutexlist[slot].pmutex); - } + // blocking wait on mutex + STL_DEBUG("attempting lock on mutex #%d <%s>", slot, mutexlist[slot].name); + status = pthread_mutex_lock(&mutexlist[slot].pmutex); if (status) - stl_error("mutex lock/trylock failed %s", strerror(status)); + stl_error("mutex_lock: <%s> failed %s", mutexlist[slot].name, strerror(status)); - STL_DEBUG("mutex UNLOCKED #%d", slot); + STL_DEBUG("mutex lock obtained #%d", slot); return 1; // unlocked, return true } +int32_t +stl_mutex_lock_noblock(int32_t slot) +{ + int status; + + if (mutexlist[slot].busy == 0) + stl_error("mutex_lock_noblock: mutex %d not allocatedq", slot); + + // non-blocking wait + status = pthread_mutex_trylock(&mutexlist[slot].pmutex); + + switch (status) { + case 0: + STL_DEBUG("test mutex - UNLOCKED #%d <%s>", slot, semlist[slot].name); + return 1; // unlocked, it's ours, return true + case EBUSY: + STL_DEBUG("test mutex - LOCKED #%d <%s>", slot, semlist[slot].name); + return 0; // still locked, return false + default: + stl_error("mutex_lock_noblock: <%s> failed %s", mutexlist[slot].name, strerror(status)); + } + + return 0; // return false +} + void -stl_mutex_unlock(int32_t slot, int32_t nowait) +stl_mutex_unlock(int32_t slot) { int status; - STL_DEBUG("lock mutex #%d <%s>", slot, mutexlist[slot].name); + STL_DEBUG("unlock mutex #%d <%s>", slot, mutexlist[slot].name); if (mutexlist[slot].busy == 0) - stl_error("unlock: mutex %d not allocatedq", slot); + stl_error("mutex_unlock: mutex %d not allocated", slot); status = pthread_mutex_unlock(&mutexlist[slot].pmutex); if (status) - stl_error("mutex lock: failed %s", strerror(status)); + stl_error("mutex_unlock: <%s> failed %s", mutexlist[slot].name, strerror(status)); } #ifdef __linux__ @@ -456,7 +544,7 @@ stl_timer_create(char *name, double interval, int32_t semid) } LIST_UNLOCK if (tp == NULL) - stl_error("too many timers, increase NTIMERS (currently %d)", NTIMERS); + stl_error("timer_create: too many timers, increase NTIMERS (currently %d)", NTIMERS); struct sigevent sevp; sevp.sigev_notify = SIGEV_THREAD; @@ -468,7 +556,7 @@ stl_timer_create(char *name, double interval, int32_t semid) status = timer_create(CLOCK_REALTIME, &sevp, &t); if (status) - stl_error("timer create: failed %s", strerror(errno)); + stl_error("timer create: <%s> failed %s", name, strerror(errno)); tp->timer = t; tp->name = stl_stralloc(name); @@ -483,7 +571,7 @@ stl_timer_create(char *name, double interval, int32_t semid) its.it_value = ts; // initial value status = timer_settime(t, 0, &its, NULL); if (status) - stl_error("timer settime failed %s", strerror(errno)); + stl_error("timer_create: <%s>, settime failed %s", name, strerror(errno)); STL_DEBUG("create timer #%d <%s>", slot, name); @@ -510,6 +598,17 @@ void stl_error(const char *fmt, ...) fprintf(stderr, "\n"); va_end(ap); + +#ifdef __APPLE__ + void* callstack[128]; + int i, frames = backtrace(callstack, 128); + char** strs = backtrace_symbols(callstack, frames); + for (i = 0; i < frames; ++i) { + printf("%s\n", strs[i]); + } + free(strs); +#endif + exit(1); } @@ -552,7 +651,8 @@ void * stl_get_functionptr(char *name) { #ifdef __linux__ - // this is ugly, but I can't figure how to make dlopen/dlsym work here + // this is ugly, but I can't figure how to make dlopen/dlsym work under Linux + // dlsym() is supported but always returns NULL. FILE *fp; char cmd[4096]; void *f; diff --git a/stl/stl.h b/stl/stl.h index fbed947..4fa6b9f 100644 --- a/stl/stl.h +++ b/stl/stl.h @@ -1,22 +1,30 @@ +/* + * Simple thread library (STL) for MATLAB Coder + * Peter Corke August 2018 + */ +#ifndef __stl_h__ +#define __stl_h__ // function signatures void stl_initialize(int argc, char **argv); -void stl_log(const char *fmt, ...); -void stl_error(const char *fmt, ...); +void stl_log(const char *fmt, ...); //__attribute__ ((format (printf, 1, 2))); +void stl_error(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); void stl_debug(int32_t debug); void *stl_get_functionptr(char *name); char *stl_stralloc(char *s); +void stl_require(void *v); // sleep void stl_sleep(double t); // threads -int32_t stl_thread_create(char *func, void * arg, char *name); +int32_t stl_thread_create(char *func, void * arg, int32_t hasstackdata); int32_t stl_thread_join(int32_t slot); void stl_thread_cancel(int32_t slot); int32_t stl_thread_self(); char * stl_thread_name(int32_t id); +int stl_thread_add(char *name); // command line arguments int32_t stl_argc(); @@ -25,9 +33,14 @@ void stl_argv(int a, char *arg, int32_t len); // semaphores int32_t stl_sem_create(char *name); void stl_sem_post(int32_t slot); -int stl_sem_wait(int32_t slot, int32_t nowait); +int stl_sem_wait(int32_t slot); +int stl_sem_wait_noblock(int32_t slot); + // mutexes int32_t stl_mutex_create(char *name); -int32_t stl_mutex_lock(int32_t slot, int32_t nowait); -void stl_mutex_unlock(int32_t slot, int32_t nowait); +int32_t stl_mutex_lock(int32_t slot); +int32_t stl_mutex_lock_noblock(int32_t slot); +void stl_mutex_unlock(int32_t slot); + +#endif \ No newline at end of file diff --git a/stl/stl.m b/stl/stl.m new file mode 100644 index 0000000..6e3d839 --- /dev/null +++ b/stl/stl.m @@ -0,0 +1,322 @@ +classdef stl < handle %#codegen +%STL simple thread library +% +% This class contains static-only methods that implement a simple thread interface +% for MATLAB Coder. +% +% Threads:: +% launch create a thread +% cancel cancel a thread +% join wait for a thread to terminate +% sleep pause a thread +% self get thread id +% +% Mutexes: +% mutex create a mutex +% mutex_lock acquire lock on mutex +% mutex_try test a mutex +% mutex_unlock unlock a mutex +% +% Semaphores:: +% semaphore create a semaphore +% semaphore_post post a semaphore +% semaphore_wait wait for a semaphore +% semaphore_try test a semaphore +% timer periodically post a semaphore +% +% Miscellaneous:: +% log send a message to log stream +% argc get number of command line arguments +% argv get a command line argument +% copy copy a variable to thwart optimization +% debug enable debugging messages +% +% Copyright (C) 2018, by Peter I. Corke + + methods(Static) + + % command line arguments + function ac = argc() + %stl.argc Return number of command line arguments + % + % stl.argc is the number of command line arguments, and is always at least one. + % + % See also: stl.argv. + coder.cinclude('stl.h'); + + ac = int32(0); + ac = coder.ceval('stl_argc'); % evaluate the C function + end + + function s = argv(a) + %stl.argv Return command line argument + % + % S = stl.argv(I) is a character array representing the Ith command line argument. + % + % Notes:: + % - I is in the range 0 to stl.argc-1. + % - I=0 returns the name of the command. + % + % See also: stl.argc. + coder.cinclude('stl.h'); + + s = ''; + coder.varsize('s'); + + BUFSIZ = 256; + buf = char(zeros(1,BUFSIZ)); % create a buffer to write into, all nulls + + coder.ceval('stl_argv', a, coder.wref(buf), BUFSIZ); % evaluate the C function + + % find the end of the string, where the first unwritten null is + for i=1:BUFSIZ-1 + if buf(i) == 0 + % found a null, return variable length array up to here + s = buf(1:i-1); + return; + end + end + end + + function debug(v) + %stl.debug Control debugging + % + % stl.debug(D) controls the display of debug messages. If D is true messages + % are enabled. + % + % See also: stl.log. + coder.ceval('stl_debug', v); + end + + function v2 = copy(v) + %stl.copy Circumvent code optimization + % + % y = stl.copy(x) makes a copy y = x in such + v2 = v; + coder.ceval('stl_require', coder.ref(v2)); + end + + % thread + function tid = launch(name, arg, stackdata) + %stl.launch Create a new thread + % + % tid = stl.launch(name) is the integer id of a new thread called name, which executes the + % MATLAB entry point name. + % + % tid = stl.launch(name, arg) as above but passes by reference the struct arg as an + % argument to the thread. Values in the struct can be shared by the caller and the thread. + % + % tid = stl.launch(name, arg, hasstackdata) as above but the logical hasstackdata indicates + % whether the MATLAB entry point requires passed stack data. + % + % Notes:: + % - The thread id is a small integer which indexes into an internal thread table. If an error + % is obtained about too few threads then increase NTHREADS in stl.c and recompile. + % - If a struct is shared between threads then access should be controlled using a mutex. The + % most convenient way to do this is for the struct to contain a mutex id and the main thread + % to allocate a mutex. + % - The only way to tell if a thread has stack data is to look at the generated .c or .h file. + % If the function has 2 arguments then hasstackdata should be true. + % + % See also: stl.cancel, stl.join, stl.mutex, stl.copy. + coder.cinclude('stl.h'); + + if nargin < 2 + arg = 0; + end + if nargin < 3 + stackdata = 0; + end + tid = int32(0); + tid = coder.ceval('stl_thread_create', cstring(name), coder.ref(arg), stackdata); % evaluate the C function + end + + function cancel(id) + %stl.cancel Cancel a thread + % + % stl.cancel(tid) cancels the thread with the specified id. + % + % See also: stl.launch. + coder.cinclude('stl.h'); + + coder.ceval('stl_thread_cancel', id ); % evaluate the C function + end + + function join(id) + %stl.join Wait for thread to exit + % + % stl.join(tid) waits until the thread with the specified id terminates. + % + % See also: stl.launch. + coder.cinclude('stl.h'); + + coder.ceval('stl_thread_join', id ); % evaluate the C function + end + + function sleep(t) + %stl.sleep Pause this thread + % + % stl.sleep(D) pauses the current thread for the specified time D. + % + % Notes:: + % - D does not have to be integer. + % - Precision ultimately depends on the system clock. + % + % See also: stl.timer. + coder.cinclude('stl.h'); + coder.ceval('stl_sleep', t); % evaluate the C function + end + + function id = self() + %stl.self Get thread id + % + % tid = stl.self() is the id of the current thread. + % + % See also: stl.launch. + coder.cinclude('stl.h'); + + id = int32(0); + id = coder.ceval('stl_thread_self'); % evaluate the C function + end + + % mutex + function id = mutex(name) + %stl.mutex Create a mutex + % + % mid = stl.mutex(name) returns the id of a new mutex with the specified name. + % + % Notes:: + % - The mutex is initially unlocked. + % - The mutex id is a small integer which indexes into an internal mutex table. If an error + % is obtained about too few mutexes then increase NMUTEXS in stl.c and recompile. + % + % See also: stl.mutex_lock, stl.mutex_try, stl.mutex_unlock. + + id = int32(0); + id = coder.ceval('stl_mutex_create', cstring(name)); % evaluate the C function + end + + function mutex_lock(id) + %stl.mutex_lock Lock the mutex + % + % stl.mutex(mid) waits indefinitely until it can obtain a lock on the specified mutex. + % + % See also: stl.mutex_unlock, stl.mutex_try. + coder.ceval('stl_mutex_lock', id); % evaluate the C function + end + + function mutex_try(id) + %stl.mutex_try Test the mutex + % + % v = stl.mutex_try(mid) returns the mutex state: false if it is currently + % locked by another thread, or true if a lock on the mutex was obtained. It never blocks. + % + % See also: stl.mutex_unlock, stl.mutex. + coder.ceval('stl_mutex_lock_noblock', id); % evaluate the C function + end + + function mutex_unlock(id) + %stl.mutex_unlock Unlock the mutex + % + % stl.mutex_unlock(mid) unlocks the specified mutex. It never blocks. + % + % See also: stl.mutex, stl.mutex_try. + coder.ceval('stl_mutex_unlock', id); % evaluate the C function + end + + % semaphore + function id = semaphore(name) + %stl.semaphore Create a semaphore + % + % sid = stl.semaphore(name) returns the id of a new semaphore with the specified name. + % + % Notes:: + % - The semaphore is initially not raised/posted. + % - The semaphore id is a small integer which indexes into an internal semaphore table. If an error + % is obtained about too few semaphores then increase NSEMAPHORES in stl.c and recompile. + % + % See also: stl.semaphore_post, stl.semaphore_wait, stl.semaphore_try. + + coder.cinclude('stl.h'); + + id = int32(0); + id = coder.ceval('stl_sem_create', cstring(name)); % evaluate the C function + end + + function semaphore_post(id) + %stl.semaphore_post Post a semaphore + % + % stl.semaphore_post(sid) posts (raises) the specified semaphore. + % + % Notes:: + % - Only one of the threads waiting on the semaphore will be unblocked. + % + % See also: stl.semaphore_wait, stl.semaphore_try. + coder.cinclude('stl.h'); + + coder.ceval('stl_sem_post', id); % evaluate the C function + end + + function semaphore_wait(id) + %stl.semaphore_wait Wait for a semaphore + % + % stl.semaphore_wait(sid) waits indefinitely until the specified semaphore is raised. + % + % See also: stl.semaphore_post, stl.semaphore_try. + coder.cinclude('stl.h'); + + coder.ceval('stl_sem_wait', id); % evaluate the C function + end + + function semaphore_try(id) + %stl.semaphore_try Test a semaphore + % + % v = stl.semaphore_try(sid) returns without blocking the semaphore status: true if raised, otherwise false. + % + % See also: stl.semaphore_post, stl.semaphore_wait. + coder.cinclude('stl.h'); + + if nargin < 2 + wait = int32(0); + end + coder.ceval('stl_sem_wait_noblock', id); % evaluate the C function + end + + % timer + function tmid = timer(name, interval, semid) + %stl.timer Create periodic timer + % + % tid = stl.timer(name, interval, semid) is the id of the timer that fires every interval seconds and + % raises the specified semaphore. + % + % Notes:: + % - The interval is a float. + % - The first semaphore raise happens at time interval after the call. + % - The timer id is a small integer which indexes into an internal timer table. If an error + % is obtained about too few timers then increase NTIMERS in stl.c and recompile. + % + % See also: stl.semaphore_wait, stl.semaphore_try. + coder.cinclude('stl.h'); + + tmid = int32(0); + tmid = coder.ceval('stl_timer_create', cstring(name), interval, semid); % evaluate the C function + end + + % logging + function log(varargin) + %stl.log Send formatted string to log + % + % stl.log(fmt, args...) has printf() like semantics and sends the formatted + % string to the log where it is displayed with date, time and thread name. + % + % NOTES:: + % - String arguments, not the format string, must be wrapped with cstring, eg. cstring('hello') + % + % See also: stl.debug. + coder.cinclude('stl.h'); + + coder.ceval('stl_log', cstring(varargin{1}), varargin{2:end} ); % evaluate the C function + end + + end % methods(Static) +end % classdef \ No newline at end of file diff --git a/stl/stllog.m b/stl/stllog.m deleted file mode 100644 index 09f2e32..0000000 --- a/stl/stllog.m +++ /dev/null @@ -1,12 +0,0 @@ - function stllog(varargin) - %stl.log Send formatted string to log - % - % stl.log(fmt, args...) has printf() like semantics and sends the formatted - % string to the log where it is displayed with date, time and thread name. - % - % NOTES:: - % - String arguments, not the format string, must be wrapped with cstring, eg. cstring('hello') - coder.cinclude('stl.h'); - - coder.ceval('stl_log', cstring(varargin{1}), varargin{2:end} ); % evaluate the C function -end diff --git a/stl/webserver.m b/stl/webserver.m index 4fd870f..8177303 100644 --- a/stl/webserver.m +++ b/stl/webserver.m @@ -1,15 +1,66 @@ classdef webserver < handle %#codegen +%webserver Lightweight webserver library +% +% This class contains methods that implement a lightweight webserver +% for MATLAB Coder. +% +% Methods:: +% webserver create a webserver instance +% debug enable debugging messages +% details display HTTP header +%- +% html send string to browser +% template send file with substitutions to browser +% file send file to browser +% data send data to browser +% error send error code to browser +%- +% url URL for current request +% isGET test for GET request +% isPOST test for POST request +% reqheader get element of HTTP header +% getarg get element of HTTP GET header +% postarg get element of HTTP POST header +% +% Copyright (C) 2018, by Peter I. Corke methods(Static) - function obj = webserver(port, callback) + function obj = webserver(port, callback, arg) + %webserver Create a webserver + % + % webserver(port, callback) creates a new webserver executing it a separate + % thread and listening on the specified port (int). The MATLAB entrypoint + % named callback is invoked on every GET and PUT request to the server. + % webserver Create a web server instance port = int32(port); coder.cinclude('httpd.h'); - coder.ceval('web_start', port, cstring(callback)); + coder.cinclude('stl.h'); + if nargin == 3 + coder.ceval('web_start', port, cstring(callback), coder.ref(arg)); + else + coder.ceval('web_start', port, cstring(callback), coder.opaque('void *', 'NULL')); + end + + end + + function debug(d) + %webserver.debug Control debugging + % + % webserver.debug(D) controls the display of debug messages. If D is true messages + % are enabled. + % + % See also: stl.log. + coder.cinclude('httpd.h'); + coder.ceval('web_debug', d); end function u = url() + %webserver.url Get the URL for the request + % + % url = webserver.url() is a character array representing the URL associated with + % this request. coder.cinclude('httpd.h'); u = ''; @@ -28,52 +79,127 @@ end end + function details() + %webserver.details Show HTTP header details + % + % webserver.details() displays the HTTP header via the logging channel. + % + % See also: stl.log. + + coder.ceval('web_show_request_header'); + end + + function error(errno, msg) + %webserver.error Send error code to browser + % + % websever.error(code, msg) send an error code (eg. 404) and message to the + % requesting browser. + coder.ceval('web_error', errno, cstring(s)); + end + function html(s) - % webserver.html Send an HTML string to browser + %webserver.html Send an HTML string to browser + % + % weserver.html(str) the string str is sent to the requesting browser. The string + % can be plain text or HTML. + % + % See also: webserver.template, webserver.error. coder.ceval('web_html', cstring(s)); end function template(filename, values) - % webserver.template Send template file with substitution to browser + %webserver.template Send template file with substitution to browser + % + % webserver.template(filename, values) sends the contents of the specified file + % to the requesting browser with substitutions. Elements of the struct values + % are substituted for special HTML tags. + % + % For example the value of values.a is substitued by: + % + % + % See also: webserver.html, webserver.error, CTemplate. coder.cinclude('httpd.h'); + - names = fieldnames(values); - for i=1:length(names) - name = names{i}; - v = values.(name); - if size(v,1) > 1 - fprintf('numeric value must be scalar or row vector'); - else - coder.ceval('web_setvalue', cstring(name), cstring(num2str(v))); + if nargin == 2 + if ~isa(values, 'struct') + stl_error('argument to template must be a struct'); + end + % get data from passed structure + names = fieldnames(values); + for i=1:length(names) + name = names{i}; + v = values.(name); + if size(v,1) > 1 + fprintf('numeric value must be scalar or row vector'); + else + if isinteger(v) + fmt = '%d'; + else + fmt = '%g'; + end + coder.ceval('web_setvalue', cstring(name), cstring(sprintf(fmt, v))); + end end end coder.ceval('web_template', cstring(filename)); end function file(filename, type) - % webserver.file Send file and content type to browser + %webserver.file Send file and content type to browser + % + % webserver.file(filename, type) send the specified file to the requesting browser, with + % the specified MIME type. + % + % See also: webserver.template, webserver.html, webserver.error. + coder.ceval('web_file', cstring(filename), cstring(type)); end function data(s, type) - % webserver.file Send data and content type to browser + %webserver.file Send data and content type to browser + % + % webserver.data(data, type) send the character array data to the requesting browser, with + % the specified MIME type. + % + % Notes:: + % - The data could be a binary string, eg. an image. + % + % See also: webserver.template, webserver.html, webserver.error. coder.ceval('web_data', s, length(s), cstring(type)); end - function v = ispost() - % webserver.ispost Test if POST request - v = coder.ceval('web_ispost'); + function v = isPOST() + %webserver.isPOST Test for POST request + % + % v = webserver.isPOST() is true if this request is an HTTP POST. + % + % See also: webserver.isGET. + v = int32(0); + v = coder.ceval('web_isPOST'); end - function v = isget() - % webserver.isget Test if GET request - v = ~coder.ceval('web_ispost'); + function v = isGET() + % webserver.isGET Test for GET request + % + % v = webserver.isGET() is true if this request is an HTTP GET. + % + % See also: webserver.isPOST. + v = int32(0); + v = coder.ceval('web_isPOST'); + v = ~v; % codegen cant do this in the one line... end function s = reqheader(name) - % webserver.reqheader Return value of request header item + %webserver.reqheader Return value of request header item + % + % v = webserver.reqheader(key) is a character array representing the value of + % the specified key in the HTTP request header. + % + % Notes:: + % - Returns empty string if the key is not found. coder.cinclude('httpd.h'); coder.varsize('s'); s = ''; @@ -82,18 +208,30 @@ function data(s, type) buf = char(zeros(1,BUFSIZ)); % create a buffer to write into, all nulls % content - % coder.ceval('web_url', coder.wref(buf), BUFSIZ); % evaluate the C function - - for i=1:BUFSIZ-1 % find the end of the string, where the first unwritten null is - if buf(i) == 0 - s = buf(1:i-1); % found a null, return variable length array up to here - return; + found = int32(0); + found = coder.ceval('web_reqheader', coder.wref(buf), BUFSIZ); % evaluate the C function + + if found + for i=1:BUFSIZ-1 % find the end of the string, where the first unwritten null is + if buf(i) == 0 + s = buf(1:i-1); % found a null, return variable length array up to here + return; + end end end end function s = getarg(name) - % webserver.getarg Return value of GET argument + %webserver.getarg Return value of GET argument + % + % v = webserver.getarg(key) is a character array representing the value of + % the specified key in the HTTP GET request header. + % + % Notes:: + % - These parameters are on the end of the URL, eg. ?key1=val1&key2=val2 + % - Returns empty string if the key is not found. + % + % See also: webserver.isGET, webserver.url. coder.cinclude('httpd.h'); coder.varsize('s'); @@ -116,7 +254,16 @@ function data(s, type) end function s = postarg(name) - % webserver.postarg Return value of POST argument + %webserver.postarg Return value of POST argument + % + % v = webserver.postarg(key) is a character array representing the value of + % the specified key in the HTTP POST request header. + % + % Notes:: + % - POST data is typically sent from the browser using
and tags. + % - Returns empty string if the key is not found. + % + % See also: webserver.isPOST, webserver.url. coder.cinclude('httpd.h'); coder.varsize('s'); s = ''; @@ -124,15 +271,18 @@ function data(s, type) BUFSIZ = 256; buf = char(zeros(1,BUFSIZ)); % create a buffer to write into, all nulls - % content - % coder.ceval('web_url', coder.wref(buf), BUFSIZ); % evaluate the C function + found = int32(0); + found = coder.ceval('web_postarg', coder.wref(buf), BUFSIZ, cstring(name)); % evaluate the C function - for i=1:BUFSIZ-1 % find the end of the string, where the first unwritten null is - if buf(i) == 0 - s = buf(1:i-1); % found a null, return variable length array up to here - return; + if found + for i=1:BUFSIZ-1 % find the end of the string, where the first unwritten null is + if buf(i) == 0 + s = buf(1:i-1); % found a null, return variable length array up to here + return; + end end end end + end % methods(Static) end % classdef \ No newline at end of file