Basic HTTP Servers

Claro has been carefully designed to be uniquely well-suited for building highly efficient, scalable web servers. Claro's "Fearless Concurrency" guarantees are explicitly an effort to make it significantly challenging to make a buggy, slow web server - and instead, Claro's novel concurrency model will lead to performant and easy-to-maintain web servers naturally falling out of even naive usages of the language.

To actually demonstrate this explicitly, Claro provides very basic support for building HTTP servers that can be used in the absence of any sort of 3rd party framework to jump you right into your first web server in Claro. This feature is largely intended as a demonstration of Claro's current capabilities, and to point towards Claro's future direction. This is by no means a complete web server framework.

Following this guide will lead you through the steps to setting up your very first web server in Claro.

HTTP Service Definition

First, you'll need to define the endpoints that your HTTP service will handle. To do this, you'll use Claro's built-in HttpService definition syntax, e.g.:

Fig 1:


HttpService Greeter {
  genericGreeting: "/genericGreeting",
  greeting: "/greeting/{name}"
}

The above defines a very simple service with two basic endpoints.

Auto-Generated HttpServer

Claro will automatically generate a pre-configured, non-blocking web server implementation for your HttpService definition by using the builtin magic function http::getBasicHttpServerForPort(). This function is implemented as a compiler intrinsic that will infer the server to automatically generate based on the type asserted on the call. So, we can get Claro to generate a web server for the example Greeter service as in the example below.

Note that no Endpoint Handlers have been implemented yet so we should actually expect the below to fail to compile and prompt us to implement them! Doing things in this order allows us to allow Claro to prompt us with the signatures that we need to implement, which is just a convenience.

Fig 2:


HttpService Greeter {
  genericGreeting: "/genericGreeting",
  greeting: "/greeting/{name}"
}

# endpoint_handlers Greeter { }

var greeterServer: HttpServer<Greeter> = http::getBasicHttpServerForPort(8080);
_ = greeterServer; # Not starting the server yet.

Compilation Errors:

http_servers_EX2_example.claro:8: Invalid HttpServer Generation Requested for HttpService Missing Endpoint Handlers Definition: In order to automatically generate an HttpServer for the given HttpService an `endpoint_handlers` block such as the following must be defined:
	endpoint_handlers Greeter {
		graph provider genericGreeting() -> future<HttpResponse> {
			...
		}
		graph function greeting(pathArg0: string) -> future<HttpResponse> {
			...
		}
	}
var greeterServer: HttpServer<Greeter> = http::getBasicHttpServerForPort(8080);
                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1 Error

Implementing Endpoint Handlers

A service definition on its own doesn't actually provide much utility without endpoint handlers implemented to actually serve up the responses to incoming requests. As we see from the compilation error above, we must define endpoint handlers for the above HttpService by defining an endpoint_handlers block with a Graph Procedure implementation corresponding to each endpoint in the HttpService definition.

Note that in the HTTP service definition above, the greeting endpoint includes {name} in the route - this is a "path parameter" that will automatically be parsed from incoming requests and passed along as input to the associated endpoint handler. So, note that the signature of the greeting endpoint handler includes a string arg that will represent the value of the {name} path parameter for each request to that endpoint.

Fig 3:


HttpService Greeter {
  genericGreeting: "/genericGreeting",
  greeting: "/greeting/{name}"
}

endpoint_handlers Greeter {
  graph provider genericGreeting() -> future<HttpResponse> {
    root httpRes <- http::getOk200HttpResponseForJson(@json);
    node json    <- EndpointHandlerImpls::GENERIC_GREETING;
  }
  graph function greeting(name: string) -> future<HttpResponse> {
    root httpRes <- http::getOk200HttpResponseForJson(@json);
    node json    <- EndpointHandlerImpls::getGreetingForName(name);
  }
}

var greeterServer: HttpServer<Greeter> = http::getBasicHttpServerForPort(8080);
_ = greeterServer; # Not starting the server yet.

As you can see, the core implementation logic was factored out into another Module EndpointHandlerImpls. These impls can do anything, including making arbitrary downstream network requests, as long as they are non-blocking. In this case, they'll simply return some simple greeting.

Note: the requirement that each endpoint handler implementation be a Graph Procedure is to ensure that the resulting web service is statically guaranteed to be non-blocking and to ensure that each request is handled off the request thread so that long-running computations don't interfere with the service's ability to receive and schedule incoming requests. This ties together all of Claro's design decisions to make building fundamentally concurrent web services a trivial task.

Starting an HttpServer

That's it! Now we can actually start the Greeter server that we just implemented. This is as simple as calling the builtin http::startServerAndAwaitShutdown() consumer. This call effectively drops into an infinite loop, so depending on how you start it, when you're done and want to bring the service down, you'll have to send a termination signal to the server process e.g. using ctrl-C.

Fig 4:


HttpService Greeter {
  genericGreeting: "/genericGreeting",
  greeting: "/greeting/{name}"
}

endpoint_handlers Greeter {
  graph provider genericGreeting() -> future<HttpResponse> {
    root httpRes <- http::getOk200HttpResponseForJson(@json);
    node json    <- EndpointHandlerImpls::GENERIC_GREETING;
  }
  graph function greeting(name: string) -> future<HttpResponse> {
    root httpRes <- http::getOk200HttpResponseForJson(@json);
    node json    <- EndpointHandlerImpls::getGreetingForName(name);
  }
}

var greeterServer: HttpServer<Greeter> = http::getBasicHttpServerForPort(8080);
_ = greeterServer; # Not starting the server yet.
# Finally start the service
http::startServerAndAwaitShutdown(greeterServer);

The below recording is a demonstration this server in action. It first starts up the server (launching the process in the background), and then sends a couple requests to each endpoint using curl to demonstrate the server in action, and then finally kills the server.

Note: The below recording was made with asciinema - try pausing and copying any text.