Preemptive scheduling coroutine API server
Tags: api, webdev, backend, http
Published: 2023-07-28 02:53:35

This article introduces the open source API server [Medge](https://github.com/Water-Melon/Medge). Medge is an HTTP server, and it is positioned as an API server.

In Medge, we can use the scripting language [Melang](https://github.com/Water-Melon/Melang) to write and implement API logic. Melang is a preemptive scheduling coroutine language, in which each script task is regarded as a coroutine running inside the same single thread, and is scheduled according to the execution step. Therefore, during the development of upper-level logic, developers do not need to consider the problem of coroutine scheduling, nor do they need to consider when to call certain functions to give up CPU execution rights.

Now let’s see a complete simple usage example.

### Example

After we have installed Medge, we can use the command *medge* to start HTTP server.

In this example, we will execute the command as shown below.

```
$ medge -p 8080 -d /opt/medge/ -w 1
```

- `-p` is used to specify the TCP port number that the HTTP server listens to, if not set, the default is 80. In this example the HTTP server listens on `0.0.0.0:8080`.
- `-d` is used to specify the uniform entry directory path of the API services, in this case it is `/opt/medge`. We will explain the tree structure of this directory later. If this parameter item is not specified, it defaults to `/opt/medge`.
- `-w` is used to specify the number of worker processes. Defaults to `1` if not specified.

At this point, the server has been started, but we have not given the specific processing logic of the API service. Then let’s add API services.

As mentioned above, the `-d` parameter of Medge specifies the unified entry directory of the API services. Below we give the approximate directory tree structure under this path:

```
|- /opt/medge/
    |- service_1/
        |- entry.m
        |- ...
    |- service_2/
        |- entry.m
        |- ...
```

It can be seen that there are two services in the `/opt/medge` directory, namely: `service_1` and `service_2`, and each service has a script file `entry.m` with the same name. `entry.m` is the entry file of the service, that is, when Medge receives an HTTP request and finds the corresponding service, it will immediately execute the `entry.m` script of the service for processing.

In our example, we only has one API service, so the directory tree in this example is:

```
|- /opt/medge/
    |- 127.0.0.1:8080/
        |- entry.m
        |- index.m
```

Here, `127.0.0.1:8080` is a directory. The reason for this name is that the service name in Medge is named and distinguished by the header field *Host* of the HTTP request. And in this example, HTTP server does not have a domain name, so the directory name here is in the form of such an IP:port.

Below, we give the script content of `entry.m` and `index.m`:

```
//entry.m
/*
 * Implement a simple controller.
 * There are three variable injected in this script task:
 *   1. Req. Its prototype is:
 *       Req {
 *           method; //string  e.g. GET POST DELETE ...
 *           version; //string e.g. HTTP/1.0 HTTP/1.1
 *           uri; //string e.g. /index/index
 *           args; //an key-value array, e.g. ["key":"val", ...]
 *           headers; //an key-value array, e.g. ["Content-Type":"application/json", ...]
 *           body; //string
 *       }
 *
 *    2. Resp. Its prototype is:
 *        Resp {
 *            version; //same as Req's version
 *            code; //integer  e.g. 200
 *            headers; //same as Req's headers
 *            body; //same as Req's body
 *        }
 *
 *.   3. Basedir. A string of the base directory path. (Not used in this example)
 */

#include "@/index.m"

str = Import('str');
sys = Import('sys');

uri = str.slice(Req.uri, '/');
ctlr = str.capitalize(uri[0]);
o = $ctlr;
if (sys.has(o, uri[1]) != 'method') {
  Resp.code = 404;
} else {
  o.__action__ = uri[1];
  Resp.body = o.__action__();
  Resp.headers['Content-Length'] = str.strlen(Resp.body);
}
```
```
//index.m

Json = Import('json');

Index {
    @index() {
        Resp.headers['Content-Type'] = 'application/json';
        return Json.encode(['code': 200, 'msg': 'OK']);
    }
}
```

It can be seen that `entry.m` contains some comment information in front of it, which mentions that each request will inject 3 variables into the `entry.m` script of its corresponding service:

- `Req` contains information about the request, such as: method, version, resource path, parameters, request headers and request body.
- `Resp` contains information about the response, such as: version (default is the same as the request version), response status code (default is 200), response header and response body.
- `Basedir` is a string containing the path specified by the Medge `-d` parameter.

`entry.m` in this example implements a very simple controller in the MVC framework, which finds the corresponding processing class and class method according to the resource path, and calls method to process the HTTP request.

In this example, we will use the following command to send an HTTP request:

```
$ curl -v http://127.0.0.1:8080/index/index
```

Therefore, in `entry.m`, the resource path `/index/index` will be sliced at first, and the first letter of it will be capitalized after obtaining the first `index`. Then use this capitalized string as the class name to find if there is a class called `Index` in the current scripting environment. After finding it, call its `index` (that is, the second `index` of the resource path) method to process the request.

After executing the `curl` command, we will see that its output is:

```
*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /index/index HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Length: 23
< Content-Type: application/json
< 
* Connection #0 to host 127.0.0.1 left intact
{"code":200,"msg":"OK"}
```

### At the end

At present, Medge is still an experimental project, and there are still many functions to be implemented. For example, it only supports HTTP/1.0 HTTP/1.1 version at present, and does not support *https* yet. In addition, Melang scripts currently only support MySQL databases, and there are no corresponding http library functions or classes for developers to use.

For more details, please visit its [Github repository](https://github.com/Water-Melon/Medge).

Thanks for reading!