Main Content

Handle Custom Routes and Payloads in HTTP Requests

In MATLAB® Production Server™, you can use deployed MATLAB functions as web request handlers. These handlers allow customization of requests and route mapping:

Web request handlers for MATLAB Production Server provide flexible client-server communication.

  • Client programmers can send custom HTTP headers and payloads in RESTful requests to the server.

  • Server administrators can provide flexible mapping of the request URLs to deployed MATLAB functions.

  • Server administrators can provide static file serving.

  • MATLAB programmers can return custom HTTP headers, HTTP status codes, HTTP status messages, and payloads in functions deployed to MATLAB Production Server.

To create a web request handler, you write a MATLAB function that accepts and returns request data, then deploy it to the server. Then, you specify custom URL routes to that function in a JSON file on the server, in a JSON file packaged into the deployed archive that contains that function, or in both files.

Write MATLAB Functions for Web Request Handler

To work as web request handlers, the MATLAB functions that you deploy to the server must accept one scalar structure array as input and return one scalar structure array as output.

The structure in the function input argument provides information about the client request. In their requests, clients can send custom HTTP headers and custom payloads. There are no data format restrictions on the payload that the deployed function can accept. For example, the function can accept raw data in binary or ASCII formats, CSV data, or JSON data that is not in the schema specified by the MATLAB Production Server RESTful API. Clients can also use the Transfer-Encoding: chunked header to send data in chunks. In chunked transfer encoding, though the server receives the payload in chunks, the input structure receives payload data in its entirety.

The structure in the function input argument contains the following fields.

Field NameData TypeDimensionsDescription
ApiVersiondouble1 x 3Version of the input structure schema in the format <major> <minor> <fix>
Bodyuint81 x NRequest payload
HeaderscellN x 2

HTTP request headers

Each element in the cell array represents a header. Each element is a key-value pair, where the key is of type char and the value can be of type char or double.

HttpVersiondouble1 x 2HTTP version in the format <major> <minor>
Methodchar1 x NHTTP request method
Pathchar1 x NPath of request URL

Since the deployed MATLAB function can accept custom headers and payloads in RESTful requests, you can vary the behavior of the MATLAB function depending on the request header data. You can use the structure in the function output argument to return a response with custom HTTP headers and payload. Server processing errors, if any, override any custom HTTP headers that you might set. If a MATLAB error occurs, the server returns an HTTP 500 Internal Server Error response. All fields in the structure are optional.

The structure in the output argument can contain the following fields:

Field NameData TypeDimensionsDescription
ApiVersiondouble1 x 3Version of the output structure schema in the format <major> <minor> <fix>
Bodyuint81 x NResponse payload
HeaderscellN x 2

HTTP response headers

Each element in the cell array represents a header. Each element is a key-value pair, where the key is of type char and the value can be of type char or double.

HttpCodedouble1 x 1HTTP status code
HttpMessagechar1 x NHTTP status message

Configure URL Routes

Custom URL routes allow the server to map the path in request URLs to any deployable archive and MATLAB function deployed on the server. You can define URL routes at the server instance level or at the deployable archive level.

  • Instance-Level Routes — Define URL routes in a single JSON file located on the server instance. These routes map request URLs to web handler functions across all archives deployed on the server. (since R2022a)

  • Archive-Specific Routes — Define URL routes for an individual archive within its own JSON file and package the file into the archive. These routes map request URLs only to web handler functions defined within that archive on the server. You can use archive-specific routes to better organize routes and avoid route matching conflicts. (since R2023b)

You can define a combination of these routes, and both types of routes are optional.

Configure Instance-Level Routes

Since R2022a

At the server instance level, to specify the mapping of a request URL to a deployed MATLAB function, you can use a JSON file present on the server. The default name of the file is routes.json and its default location is in the $MPS_INSTALL/config folder. You can change the file name and its location by changing the value of the routes-file property in the main_config server configuration file. You must restart the server after making any updates to routes.json.

When the server starts, it tries to read the routes.json file. If the file does not exist or contains errors, the server does not start and writes an error message to the main.log file present in the folder that the log-root property specifies.

The default routes.json file contains a version field with a value of 1.0.0, and an empty pathmap field. version specifies the schema version of the file. You do not need to change its value. To allow custom routes, edit the file to specify mapping rules in the pathmap array. Each object in the array corresponds to a URL route.

The instance-level routes JSON file follows this schema.

{
  "version": "1.0.0",
   "pathmap": [
       {
           "match": "<regular_expression>",
           "webhandler": {
               "component": "<name_of_deployable_archive>",
               "function": "<name_of_deployed_function>"
           }
       },
	{
           "match": "<regular_expression>",
           "webhandler": {
               "component": "<name_of_deployable_archive>",
               "function": "<name_of_deployed_function>"
           }
       }
    ]
}

To specify a URL mapping rule, use the match and webhandler fields from the pathmap array.

  • The client request URL matches a route only if it includes a slash after the hostname and port, as in http://host:port/.

  • In the match field, specify a regular expression that uses ECMAScript grammar to match the path in a request URL.

    • If the request URL matches multiple regular expressions in the match field, the first match starting from the beginning of the file is selected. Therefore, specify pattern matches in order from more specific to more general.

    • The regular expression patterns are considered a match if any substring of the request URL is a match. For example, the pattern a/b matches a/b, /a/b, and /x/a/b/y. However, you can use the regular expression anchors ^ and $ to match positions before or after specific characters. For example, the pattern ^a/b$ only matches a/b.

    • You can specify regular expressions that match query parameters in the request URL. For example, ^/clientrequest($|\\W+) matches both /clientrequest and /clientrequest followed by a non-word character and one or more additional characters, such as /clientrequest?foo=bar. The non-word character expression \\W requires an extra backslash to escape the JSON.

    • Asynchronous request execution using the MATLAB Production Server RESTful API is not supported. Request execution is synchronous. For more information about the MATLAB Production Server RESTful API, see RESTful API for MATLAB Function Execution.

  • In the webhandler field, use the component field to specify the name of the deployable archive and the function field to specify the name of the deployed function for the request URL to execute.

Configure Archive-Specific Routes

Since R2023b

Using archive-specific routes, you can organize routes by deployable archive name and avoid potential route matching conflicts that can occur when specifying all routes in the instance-level routes JSON file.

Using archive-specific routes requires MATLAB Production Server R2023b or later and MATLAB Runtime R2023b or later.

Note

  • If a client request URL matches a route in both the instance-level routes JSON file specified by the routes-file configuration property and in an archive-specific routes file, the instance-level route takes precedence.

  • If you define archive-specific routes, then your server instance must include an instance-level routes JSON file, even if you do not specify any routes in this file. Do not delete this file or comment out the routes-file configuration property.

The routes JSON file for archive-specific routes follows this schema.

{
  "version": "1.0.0",
   "pathmap": [
       {
           "match": "<regular_expression>",
           "webhandler": {
               "function": "<name_of_deployed_function>"
           }
       },
	{
           "match": "<regular_expression>",
           "webhandler": {
               "function": "<name_of_deployed_function>"
           }
       }
    ]
}

This schema is identical to the one defined for instance-level routes, except that the component field for specifying the deployable archive is optional. If specified, this field accepts as input only an empty string ("") or the name of the deployable archive, without the .ctf extension.

For example, for routes packaged into archive file myarchive.ctf, these webhandler specifications are valid and equivalent.

  • "webhandler": { "function": "myfunction", "component": "myarchive" }

  • "webhandler": { "function": "myfunction", "component": "" }

  • "webhandler": { "function": "myfunction" }

Use the component field to transfer routes defined in the instance-level routes JSON file into the archive-specific routes file without having to modify the pathmap object.

Archive-specific routes follow similar matching rules as instance-level routes. However, matching starts at the second path delimiter and potentially matches only if the client request URL includes the archive name, followed by a slash.

http://host:port/archive_name/

To package an instance-level routes JSON file into a deployable archive, use the RoutesFile option for compiler.build.productionServerArchive (MATLAB Compiler SDK).

compiler.build.productionServerArchive({mfilename1 mfilename2 ... mfilenameN}, ...
ArchiveName='archive_name', ...
RoutesFile='ArchiveRoutes.json')
Alternatively, use the ROUTES syntax of the mcc (MATLAB Compiler) function.
mcc -U -W 'CTF:archive_name,ROUTES:ArchiveRoutes.json' mfilename1 mfilename2 ... mfilenameN
Both workflows require MATLAB Compiler SDK™

  • archive_name — Name of the deployable archive you want to create. Example: myarchive.

  • ArchiveRoutes.json — Absolute or relative path to the JSON file containing archive-specific routes. Example: myarchive_routes.json.

  • mfilename1 mfilename2 ... mfilenameN — MATLAB function files, separated by spaces, that you want to package into the deployable archive. Example: myfunction1.m myfunction2.m.

Deploy Web Request Handlers with Instance-Level Routes

Since R2022a

This example shows how to deploy web request handlers in which the defined URL route mappings apply across all archives deployed on the server instance.

Prerequisites

  • You have a server instance running at the default host and port: localhost:9910. For information on starting a server, see Start Server Instance Using Command Line.

  • Your server instance uses the default instance-level route JSON file, as specified by the routes-file configuration property. The default routes JSON file is located at $MPS_INSTALL/config/route.json.

  • Your server instance uses the default auto-deployment folder, as specified by the auto-deploy-root configuration property. The default auto-deploy folder is located at $MPS_INSTALL/auto_deploy.

Write Web Handler Functions

Write three MATLAB functions that act as web request handlers. These functions use the input argument structure request, whose fields provide information about the request headers and body. Each function also constructs and returns a structure response that has fields containing a success HTTP code, a status message, custom headers, and a message body.

  • This function returns the text "Hello, name" in the response body, where name is a value provided in the query parameter of a request URL. For example, ?name=MathWorks. Save this function code to a file named helloNameHandler.m.

    function response = helloNameHandler(request)
        
        params = extractAfter(request.Path,"?");
        name = extractBetween(params,"name=",regexpPattern("($|&)"));
        data = char(sprintf("Hello, %s", name{1}));
        
        response = struct( ...
            'ApiVersion',[1 0 0], ...
            'HttpCode',200, ...
            'HttpMessage','OK', ...
            'Headers', {{'Server' 'WebFunctionTest/1'; ...
                         'X-MyHeader' 'foobar'; ...
                         'X-Request-Body-Len' sprintf('%d', length(request.Body)); ...
                         'Content-Type' 'text/plain';}}, ...
            'Body', unicode2native(data,'ISO-8859-1'));
    end
  • This function displays the text "Hello, World". Save this function code to a file named helloWorldHandler.m.

    function response = helloWorldHandler(request)
        
        data = 'Hello, World';
        
        response = struct( ...
            'ApiVersion',[1 0 0], ...
            'HttpCode',200, ...
            'HttpMessage','OK', ...
            'Headers', {{'Server' 'WebFunctionTest/1'; ...
                         'X-MyHeader' 'foobar'; ...
                         'X-Request-Body-Len' sprintf('%d', length(request.Body)); ...
                         'Content-Type' 'text/plain';}}, ...
            'Body', unicode2native(data,'ISO-8859-1'));
    end
  • This function acts as a default handler and provides information about the other routes. Save this function code to a file named defaultHandler.m.

    function response = defaultHandler(request)
        
        data = 'Use routes /hello and /hello?name=<yourname>.';
    
        response = struct( ...
            'ApiVersion',[1 0 0], ...
            'HttpCode',200, ...
            'HttpMessage','OK', ...
            'Headers', {{'Server' 'WebFunctionTest/1'; ...
                         'X-MyHeader' 'foobar'; ...
                         'X-Request-Body-Len' sprintf('%d', length(request.Body)); ...
                         'Content-Type' 'text/plain';}}, ...
            'Body', unicode2native(data,'ISO-8859-1'));
    end

Package Web Handler Functions

Package the three functions into deployable archives by using compiler.build.productionServerCompiler or the mcc command. For other ways to create deployable archives, see Create Deployable Archive for MATLAB Production Server.

  • Package helloNameHandler.m and helloWorldHandler.m into a deployable archive named hello.ctf.

    compiler.build.productionServerArchive({'helloNameHandler.m','helloWorldHandler.m'},...
        'ArchiveName','hello');
    mcc -U -W 'CTF:hello' helloNameHandler.m helloWorldHandler.m
  • Package defaultHandler.m into a deployable archive named default.ctf.

    compiler.build.productionServerArchive({'defaultHandler.m'},...
        'ArchiveName','default');
    mcc -U -W 'CTF:default' defaultHandler.m

Deploy Web Handler Functions

Deploy the hello.ctf and default.ctf archives to your server instance by copying them into the $MPS_INSTALL/auto_deploy folder. For additional ways to deploy archives to a server instance, see Deploy Archive to MATLAB Production Server.

Configure Instance-Level Routes

In the $MPS_INSTALL/config folder, update the routes.json file to map client requests to the deployed functions. Replace the existing JSON with the following.

{
  "version": "1.0.0",
  "pathmap": [
    {
        "match": "^/hello\\?name=(\\w+|$)",
        "webhandler": {
            "component": "hello",
            "function": "helloNameHandler"
        }
    },
    {
      "match": "^/hello$",
      "webhandler": {
          "component": "hello",
          "function": "helloWorldHandler"
      }
    },
    {
      "match": "^/",
      "webhandler": {
          "component": "default",
          "function": "defaultHandler"
      }
    }         
  ]
}

  • The "^/hello\\?name=(\\w+|$)" route maps request URLs of the format "localhost:9910/hello?name=name" to the helloNameHandler function.

  • The "^/hello$" route maps request URLs of the format "localhost:9910/hello" to the helloWorldHandler function.

  • The "^/" route maps request URLs that contain a slash (for example, "localhost:9910/", "localhost:9910/goodbye" and "localhost:9910/a/b/c") to the defaultHandler function.

Restart the server instance for the changes to take effect. See mps-restart.

Send Client Requests to Web Handler Functions

Use a client of your choice to send HTTP requests to the deployed functions, such as cURL or a web browser.

The following command uses cURL to call one of the deployed functions from the system command line. The output includes information about the response, including the body text and the custom headers.

curl -v http://localhost:9910/hello?name=MathWorks
*   Trying 127.0.0.1:9910...
* Connected to localhost (127.0.0.1) port 9910 (#0)
> GET /hello?name=MathWorks HTTP/1.1
> Host: localhost:9910
> User-Agent: curl/8.0.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: WebFunctionTest/1
< X-MyHeader: foobar
< X-Request-Body-Len: 0
< Content-Type: text/plain
< Content-Length: 16
< Connection: Keep-Alive
<
Hello, MathWorks* Connection #0 to host localhost left intact

This table shows additional sample request URLs you can try and which routes they match.

Request URLMatching Behavior
http://localhost:9910/Matches the "^/" route entry.
http://localhost:9910/helloMatches the "^/hello" route entry.
http://localhost:9910/hello2Matches the "^/" route entry and not "^/hello$" because the request URL does not end with the string "hello".
http://localhost:9910/hello?name=MathWorksMatches the "^/hello\\?name=(\\w+|$)" route entry.
http://localhost:9910/hello?foo=bar&name=MathWorksMatches the "^/" route entry and not "^/hello\\?name=(\\w+|$)" because the request URL can specify the name query parameter only.

Deploy Web Request Handlers with Archive-Specific Routes

Since R2023b

This example shows how to deploy web request handlers in which the URL route mappings apply only to a specific archive deployed on the server.

Prerequisites

  • You have a server instance running at the default host and port: localhost:9910. For information on starting a server, see Start Server Instance Using Command Line.

  • Your server instance uses the default instance-level route JSON file, as specified by the routes-file configuration property. The default routes JSON file is located at $MPS_INSTALL/config/route.json.

  • Your server instance uses the default auto-deployment folder, as specified by the auto-deploy-root configuration property. The default auto-deploy folder is located at $MPS_INSTALL/auto_deploy.

In addition, to run this example, you must use MATLAB Production Server R2023b or later and MATLAB Runtime R2023b or later.

Write Web Handler Functions

Write two MATLAB functions that act as web request handlers. These functions use an input argument structure request, whose fields provide information about the request headers and body. Each function also constructs and returns a structure response that has fields containing a success HTTP code, a status message, custom headers, and a message body.

  • This function displays the text "Hello, name", where name is a value provided in the query parameter of a request URL (for example, ?name=MathWorks). If the request does not contain a query parameter, it displays "Hello" instead. Save this function code to a file named helloHandler.m.

    function response = helloHandler(request)
        
        params = extractAfter(request.Path,"?");
        name = extractBetween(params,"name=",regexpPattern("($|&)"));
    
        if isempty(name) || strcmp(name, "")
            data = 'Hello';
        else
            data = char(sprintf("Hello, %s", name{1}));
        end
        
        response = struct( ...
            'ApiVersion',[1 0 0], ...
            'HttpCode',200, ...
            'HttpMessage','OK', ...
            'Headers', {{'Server' 'WebFunctionTest/1'; ...
                         'X-MyHeader' 'foobar'; ...
                         'X-Request-Body-Len' sprintf('%d', length(request.Body)); ...
                         'Content-Type' 'text/plain';}}, ...
            'Body', unicode2native(data,'ISO-8859-1'));
    end
  • This function is similar to the previous function but displays "Goodbye, name" or "Goodbye" instead. Save this function code to a file named goodbyeHandler.m.

    function response = goodbyeHandler(request)
        
        params = extractAfter(request.Path,"?");
        name = extractBetween(params,"name=",regexpPattern("($|&)"));
    
        if isempty(name) || strcmp(name, "")
            data = 'Goodbye';
        else
            data = char(sprintf("Goodbye, %s", name{1}));
        end
        
        response = struct( ...
            'ApiVersion',[1 0 0], ...
            'HttpCode',200, ...
            'HttpMessage','OK', ...
            'Headers', {{'Server' 'WebFunctionTest/1'; ...
                         'X-MyHeader' 'foobar'; ...
                         'X-Request-Body-Len' sprintf('%d', length(request.Body)); ...
                         'Content-Type' 'text/plain';}}, ...
            'Body', unicode2native(data,'ISO-8859-1'));
    end

Package and Deploy Web Handler Functions

Package the helloHandler and goodbyeHandler functions into the same archive and deploy it to the server. Include in the packaged archive a JSON file containing the URL routes to these functions.

  1. Create an archive-specific JSON file to define the routes to the two functions. Save the following JSON to a file named greet.json.

    {
      "version": "1.0.0",
      "pathmap": [
        {
            "match": "^/hello$",
            "webhandler": {
                "function": "helloHandler"
            }
        },
        {
            "match": "^/hello\\?name=(\\w+|$)",
            "webhandler": {
                "function": "helloHandler"
            }
        },
        {
            "match": "^/goodbye$",
            "webhandler": {
                "function": "goodbyeHandler"
            }
        },
        {
            "match": "^/goodbye\\?name=(\\w+|$)",
            "webhandler": {
                "function": "goodbyeHandler"
            }
        }       
      ]
    }
    • The first two routes map request URLs of the format "localhost:9910/archive_name/hello" or "localhost:9910/archive_name/hello?name=name" to the helloHandler function.

    • The last two routes map request URLs of the format "localhost:9910/archive_name/goodbye" or "localhost:9910/archive_name/goodbye?name=name" to the goodbyeHandler function.

    • archive_name is the name of the deployable archive, which you create in the next step.

  2. Package helloHandler.m and goodbyeHandler.m into a deployable archive named greet.ctf. Use the RoutesFile of the compiler.build.productionServerArchive function or the ROUTES syntax of the mcc command to package the hello.json routes file into the archive.

    compiler.build.productionServerArchive({'helloHandler.m', 'goodbyeHandler.m'}, ...
    ArchiveName='greet', ...
    RoutesFile='greet.json')
    mcc -U -W 'CTF:greet,ROUTES:greet.json' helloHandler.m goodbyeHandler.m
  3. Deploy the greet.ctf archive to your server instance by copying it into the $MPS_INSTALL/auto_deploy folder of your server instance. You do not need to restart the server for your changes to take effect.

Send Client Requests to Web Handler Functions

Send HTTP requests to the deployed functions using a client of your choice, such as cURL or a web browser.

The following command uses cURL to call one of the deployed functions from the system command line. The output includes information about the response, including the body text and the custom headers.

curl -v http://localhost:9910/greet/hello?name=MathWorks
*   Trying 127.0.0.1:9910...
* Connected to localhost (127.0.0.1) port 9910 (#0)
> GET /hello?name=MathWorks HTTP/1.1
> Host: localhost:9910
> User-Agent: curl/8.0.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: WebFunctionTest/1
< X-MyHeader: foobar
< X-Request-Body-Len: 0
< Content-Type: text/plain
< Content-Length: 16
< Connection: Keep-Alive
<
Hello, MathWorks* Connection #0 to host localhost left intact

This table shows additional sample request URLs you can try and which routes they match.

Request URLMatching Behavior
http://localhost:9910/greet/helloMatches the "^/hello$" route entry.
http://localhost:9910/greet/hello?name=MATLABMatches the "^/hello\\?name=(\\w+|$)" route entry.
http://localhost:9910/greet/goodbyeMatches the "^/goodbye$" route entry.
http://localhost:9910/greet/goodbye?name=MATLABMatches the "^/goodbye\\?name=(\\w+|$)" route entry.
http://localhost:9910/helloDoes not match any route entries because the request URL does not include the path segment containing the archive name.

Related Topics