Deploy as endpoint to MATLAB Production Server

It is possible to add a live endpoint to MATLAB Production Server which can return the OpenAPI spec for the functionality deployed to the instance. It is for example possible to wrap the OpenAPI spec generator in a Custom Routes and Payloads function which first queries the server’s discovery endpoint, then uses the generator to translate this to an OpenAPI spec and then returns the OpenAPI YAML document.

An example wrapper is included in the package:

Software/MATLAB/examples/mps/openapiEndpoint.m
function res = openapiEndpoint(req)
%OPENAPIENDPOINT MATLAB Production Server wrapper for prodserver.openapi 
%
% This wrapper facilitates adding a live endpoint to MATLAB Production
% Server which returns an OpenAPI spec for the functionality served on the
% server. This is accomplished by first querying the server's discovery
% endpoint, translating the proprietary discovery format to OpenAPI format
% and then returning the OpenAPI YAML document.
%
% IMPORTANT: the code in this example may need to be customized, especially
% the part which queries the live MATLAB Production Server discovery
% endpoint. There is no dedicated internal endpoint for this and therefore
% this wrapper will need to query the generic (external) endpoint. How this
% can or should be done will depend on the specific MATLAB Production
% Server deployment. It for example matters whether the server is
% accessible over HTTP or over HTTPS only and for HTTPS it may be relevant
% which certificate is used.
%
% Other parts of the implementation can be changed as well, it is for
% example possible to choose whether or not to include the asynchronous
% interface (by default) or to include authentication information, etc.
%
% Further, the implementation uses Custom Routes and Payloads in order to
% be able to directly return the YAML document and not output the data in
% MATLAB Production Server JSON format.

% Copyright 2023-2024 The MathWorks, Inc.

%% Parse the request
% In this example we allow the endpoint to be queried with a query
% parameter 'async' which can be set to true or false (default = false),
% i.e. you can query http://localhost:9910/api/openapi?async=true
% In that case we include the asynchronous interface in the OpenAPI
% spec. This can be customized, e.g. the default can be changed, the option
% can be removed entirely, or you can add additional other options in a
% similar way to allow configuring other aspects of the OpenAPI spec
% generation.

% Parse the Path of the request into a MATLAB URI 
uri = matlab.net.URI(req.Path);
% Which then allows easy access to query parameters
q = uri.Query;
% If there is a parameter 'async' and it is set to true, enable including
% the async interface in the spec
if ~isempty(q) && any([q.Name]=="async") && q([q.Name]=="async").Value == "true"
    async = true;
else % if not set, default to false
    async = false;
end

%% Query the discovery endpoint
% In most cases, just use default weboptions, but this can be customized
% for example to allow working with a self-signed certificate if working
% over https.
opts = weboptions();
% Query the discovery endpoint with the specified options. This may have to
% be updated to work over https and/or the server name may have to be
% changed.
discovery = webread('http://localhost:9910/api/discovery', opts);

%% Translate discovery document to OpenAPI spec
% In this example the Async setting is configurable through a query
% parameter, pass along the chosen value to the generator. This can be
% changed/customized, the settings can always be turned on or off or other
% options can be set, either hardcoded or also configurable through a query
% parameter.
openapiGenerator = prodserver.openapi(Async=async);
spec = openapiGenerator.generate(discovery);

%% Return the OpenAPI spec
% This should not require any customization; this simply returns the
% generated spec in the correct encoding, with the right HTTP headers set.
res = struct( ...
    'HttpCode',200, ...
    'HttpMessage',matlab.net.http.StatusCode(200).getReasonPhrase, ...
    'Headers',{{'Content-Type','application/openapi+yaml'}}, ...
    'Body',unicode2native(spec,'UTF-8'));

This is “just an example” as it is meant to be customized. For example it is possible to configure whether or not to include the asynchronous interface, or to add authentication information, etc. Most importantly however, the code which queries the discovery endpoint may have to be customized; see the next section.

Querying the discovery endpoint

There is no dedicated internal or loopback endpoint or function inside MATLAB Production Server which allows retrieving the discovery document directly from MATLAB code running on the instance. And so, the wrapper MATLAB code will have to query the “external” /api/discovery endpoint in the same way as any other MATLAB Production Server client would.

Hint

The discovery endpoint needs to be enabled for it to be available, see --enable-discovery.

From the MATLAB wrapper point of view, the easiest option is to query the discovery endpoint over http (and not https), and ideally the server can simply refer to itself by localhost (or 127.0.0.1). In that case the code does not have to be customized per MATLAB Production Server instance and the same CTF-archive can be used across different MATLAB Production Server instances.

  • If http is enabled on the instance anyway, this should indeed simply work.

  • If http has been disabled on the instance and it is configured to listen on https only:

    • It may be worth considering simply enabling http again. Note that it is possible to configure the instance to only listen on a specific network interface (see the –http host:port option), i.e. it is possible to enable http for localhost or 127.0.0.1 only such that only local applications on the same machine can access the instance over http but all external traffic still has to go over https. And/or it is possible to configure the http endpoint to listen on a port which is explicitly blocked for all external traffic. Some firewalls may even be able to limit the internal traffic and only allow specific processes to access the port or only allow local communication between processes running under the same user account.

    • It is also possible to query the /discovery/api endpoint over https but:

      • It is unlikely then that the instance can be referenced by localhost as the https certificate is unlikely to be valid for hostname localhost. Ensure to update the code to refer to the correct hostname.

      • If the https certificate is self-signed (or at least not signed by an certificate authority which MATLAB trusts by default) the request will have to be configured to accept the server certificate explicitly.

        1. Download the certificate in PEM-format and for example save it as YourServerCertificate.pem.

        2. Update the MATLAB code to accept this certificate when querying the endpoint:

          opts = weboptions('CertificateFilename','YourServerCertificate.pem');
          discovery = webread('https://example.com/api/discovery',opts);
          

      Note

      Technically it is also possible to disable certificate trust validation altogether by setting CertificateFilename to an empty string ''. And it is even possible to disable host name verification when switching to working with the HTTP interface in MATLAB:

      % DANGER: Disable both hostname as well as certificate trust validation
      opts = matlab.net.http.HTTPOptions('CertificateFilename','','VerifyServerName',false);
      % Start a new request
      req = matlab.net.http.RequestMessage();
      % Perform the request with the specified options
      result = req.send('https://localhost:9920/api/discovery',opts);
      % Obtain the response body data
      discovery = result.Body.Data;
      

      These options are not recommended however and should only be considered for temporary testing.

Summary

The following points can be taken into consideration when choosing how to access the discovery endpoint. It is important however that in the end you make your own security assessment and you choose the option(s) which meet(s) your requirements.

  1. http is easier from the MATLAB wrapper point of view, there are no certificates in play and the instance should be able to simply refer to itself as localhost. Also this requires just a single CTF-archive which can be used across different MATLAB Production Server instances.

  2. In general https communication is more secure but involves certificates which will likely require customization of the wrapper. The customization will likely differ per MATLAB Production Server instance. Each instance may require a custom CTF-archive specifically build for that specific instance.

  3. Some security concerns related to using http may be mitigated by simply binding the http listener to localhost only. This is done through a simple configuration option on the MATLAB Production Server instance.

  4. Concerns with http which are not addressed by 3. may require further (more complex) firewall configurations. In that case, due to this added complexity, the advantages of 1. may no longer outweigh the disadvantages of 2. and it might be easier to stick/switch to working over https only.

Customizing other aspects of the wrapper

As shown in the example, other aspects of the wrapper can be customized as well. It is possible to configure the various options for the OpenAPI spec generator. It is possible to customize the “interface” of the endpoint, e.g. add additional query parameters to allow configuring the behavior. See Write MATLAB Functions for Web Request Handler in the MATLAB Production Server documentation to learn more about implementing these kinds of functions.

URL Routes

A custom route must be configured to make the endpoint work correctly. As documented, routes can be configured on an instance level or archive level (in releases R2023b and newer). The easiest option is to make use of archive level routes as this requires no further configuration and restart of the MATLAB Production Server instance(s). An example archive level routes file is included as routes.json:

Software/MATLAB/examples/mps/routes.json
{
    "version": "1.0.0",
     "pathmap": [
         {
             "match": "^/spec",
             "webhandler": {
                 "function": "openapiEndpoint"
             }
         }
      ]
  }

If an instance level configuration is desired/required, update the routes configuration file (typically config/routes.json) on the instance(s).

Compile

To build the CTF-archive with archive level routes, use:

compiler.build.productionServerArchive(...
    'openapiEndpoint.m',...
    ArchiveName='openapi',...
    RoutesFile='routes.json');

Note that with archive level routes, the endpoint will become http(s)://example.com/ArchiveName/RouteAsDefinedInRoutesFile.

Choose ArchiveName and configure the routes file appropriately to make the functionality available at the desired endpoint. E.g. with the routes file as included in the example and the archive name used above, the final endpoint becomes http://example.com/openapi/spec.

Deploy

As with any other component, copy the resulting CTF-archive to the auto_deploy folder of the MATLAB Production Server instance(s). If working with instance level routes, restart the MATLAB Production Server instance after having updated the routes configuration file.