https://kore.io

Reading request parameters

2018-05-15

In the previous blog post, I talked about how you could configure your application to allow request parameters to be validated through Kore.

Today lets take a look at how to read request parameters directly from your application. Let's be more practical this time around and actually write some code!

Installing Kore

Before we dive into this, we need to set up a default Kore installation. You will want the latest master branch from Github.

$ git clone https://github.com/jorisvink/kore
$ cd kore
$ make && sudo make install

The above will install Kore using PREFIX=/usr/local by default. If you want to change PREFIX to something else, set it as an environment variable before you run the make and make install targets.

For example, installing everything under ~/kore_test:

$ git clone https://github.com/jorisvink/kore
$ cd kore
$ export PREFIX=~/kore_test
$ make && make install
$ export PATH=~/kore_test/bin:$PATH

Creating the application

Now that Kore is installed let us use the kodev tool that comes with it to create a new application skeleton:

$ kodev create parameters
created parameters/src/parameters.c
created parameters/conf/parameters.conf
created parameters/conf/build.conf
created parameters/.gitignore
created dh2048.pem
parameters created successfully!
WARNING: DO NOT USE THE GENERATED DH PARAMETERS AND CERTIFICATES IN PRODUCTION
$ cd parameters

Because a default Kore build will always have TLS enabled the kodev tool will automatically generate a self-signed certificate and some reasonable DH parameters for you.

We can now start editing the src/parameters.c file. Open it up in your favorite $EDITOR.

1 : #include <kore/kore.h>
2 : #include <kore/http.h>
3 :
4 : int             page(struct http_request *);
5 :
6 : int
7 : page(struct http_request *req)
8 : {
9 :        http_response(req, 200, NULL, 0);
10:        return (KORE_RESULT_OK);
11:
12: }

You will see that a default page handler called page was created and returns a status code of 200 without any data.

We are going to modify this code, so the handler only accepts GET methods, will try to read the id query string parameter if present and will return some message.

Allowing GET only

Let's start with limiting the handler to only GET requests. We can do this by looking at the method member of the struct http_request data structure that was passed in as the argument. This data structure contains all the information about the HTTP request.

So let's add this code after line 8:

	if (req->method != HTTP_METHOD_GET) {
		http_response_header(req, "Allow", "GET");
		http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
		return (KORE_RESULT_OK);
	}

If the method for the request was not GET, we will add a response header Allow with a value of GET and return a 400.

Let's see if it worked. We can start the Kore application by merely running kodev run in the application directory. The kodev tool will automatically rebuild the code where required and then start Kore in foreground mode. You can send a SIGINT when in foreground mode to cleanly shutdown the server.

$ kodev run
building parameters (dev)
compiling parameters.c
parameters built successfully!
[parent]: running on https://127.0.0.1:8888
[parent]: kore is starting up
[keymgr]: no rand_file location specified
[wrk 1]: worker 1 started (cpu#1)
[keymgr]: key manager started

After getting the server up and running, we can test if our page handler only accepts GET requests:

$ curl -i -k -X PUT -d "" https://127.0.0.1:8888
HTTP/1.1 400 Bad Request
server: kore (3.0.0-devel)
connection: keep-alive
keep-alive: timeout=20
strict-transport-security: max-age=31536000; includeSubDomains
allow: get
content-length: 0
$

That seems to have worked. We can move on to dealing with the query parameters. Stop Kore by sending a SIGINT (hit CTRL-C).

Dealing with the request parameter

We will need to configure a params block in the configuration to let Kore know that we are expecting a query string parameter called id. So open conf/parameters.conf:

1 : # parameters configuration
2 :
3 : bind            127.0.0.1 8888
4 : load            ./parameters.so
5 :
6 : tls_dhparam     dh2048.pem
7 :
8 : domain * {
9 :        certfile        cert/server.pem
10:        certkey         cert/key.pem
11:
12:        static  /       page
13: }

First, we add our validator after line 6:

validator v_id regex ^[0-9]*$

The v_id validator is a normal regular expression validator. Now that we have that we can add the params configuration after line 12:

        params qs:get / {
                validate id v_id
        }

Breaking that configuration down: we are instructing Kore that for GET requests to '/' the query string should contain a parameter called id which is to be validated according to the to v_id validator.

With all the configuration in place, we can go ahead and write some code to read this id parameter if it validated properly.

Kore has several functions to convert a request parameter into a native C data types. Additionally, those functions will always constraint check the parameter before returning a result. For example, if you want the id parameter as an unsigned 16-bit integer, Kore will automatically make sure the parameter falls between 0..USHRT_MAX. If it does not the function call automatically fails.

Following the previous paragraph, let us add some code that does precisely that: read the id parameter as an unsigned 16-bit integer and return "ok" to the caller if it was successful. Otherwise, we will return a 400 bad request. Open src/parameters.c again and update it, so it looks like the following:

1 : #include <kore/kore.h>
2 : #include <kore/http.h>
3 :
4 : int             page(struct http_request *);
5 :
6 : int
7 : page(struct http_request *req)
8 : {
9 :        u_int16_t       id;
10:
11:        if (req->method != HTTP_METHOD_GET) {
12:                http_response_header(req, "allow", "get");
13:                http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
14:                return (KORE_RESULT_OK);
15:        }
16:
17:        http_populate_get(req);
18:
19:        if (http_argument_get_uint16(req, "id", &id)) {
20:                kore_log(LOG_INFO, "id: %u", id);
21:                http_response(req, HTTP_STATUS_OK, "ok", 2);
22:        } else {
23:                http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
24:        }
25:
26:        return (KORE_RESULT_OK);
27:
28: }

On line 17, we ask Kore to parse the query string. This will cause Kore to automatically validate the parameters according to the params block matching the URI, which we configured earlier.

On line 19, we attempt to get the id parameter as a unsigned 16-bit integer and store the result in the id variable on the stack. If successful we use the kore_log() function on line 20 to display it on the console and return an HTTP_STATUS_OK (200) on line 21 back the client with a body of "ok".

If it failed because the id parameter did not pass validation or it did not fit inside of a unsigned 16-bit integer we send a bad request (400) back on line 23.

Let us try this, start kore again in the same way as before.

First, without any id parameter:

$ curl -i -k "https://127.0.0.1:8888"
HTTP/1.1 400 Bad Request
server: kore (3.0.0-devel)
connection: keep-alive
keep-alive: timeout=20
strict-transport-security: max-age=31536000; includeSubDomains
content-length: 0
$

Next, we try with an id parameter in the range of 0..USHRT_MAX:

$ curl -i -k "https://127.0.0.1:8888?id=788"
HTTP/1.1 200 OK
server: kore (3.0.0-devel)
connection: keep-alive
keep-alive: timeout=20
strict-transport-security: max-age=31536000; includeSubDomains
content-length: 2

ok
$

Finally, a request where the id parameter does not fall in the correct range but is still validated according to our v_id validator.

$ curl -i -k "https://127.0.0.1:8888?id=287301"
HTTP/1.1 400 Bad Request
server: kore (3.0.0-devel)
connection: keep-alive
keep-alive: timeout=20
strict-transport-security: max-age=31536000; includeSubDomains
content-length: 0

It all just works. Fantastic!

The full source for this example used in the blog is available on Github.

In the next blog post I will talk more in detail how the kodev tool that was used in this post works. Including how to setup single binary builds, use external libraries, etc.

.joris

kore-blog v0.2