Skip to content

Latest commit

 

History

History
119 lines (94 loc) · 3.8 KB

README.md

File metadata and controls

119 lines (94 loc) · 3.8 KB

RestInterfaces.jl

Project Status: Active – The project has reached a stable, usable state and is being actively developed.

Overview

This is a lightweight Julia library for implementing basic REST APIs. It was created to provide a means of programming these APIs using interfaces. It sits on-top of the venerable HTTP.jl.

An Example

The following is an example of a small Hello, World! server.

using HTTP
# HttpError-themed exceptions can be found here; these acheive a special synergy
# with the error-handling middleware we're using below
using RestInterfaces.HttpErrors: unprocessable_entity
# As one might have guessed, abstractions over HttpMethods
using RestInterfaces.HttpMethods: Get, Post
# Several pre-made middleware functions can be found here
# This one converts HttpErrors into Http.Responses
using RestInterfaces.Middleware: handle_errors
# Composable routers with minimal pattern-matching facilities
using RestInterfaces.Middleware.Routers: Router, route
# Resource abstractions
using RestInterfaces.Resources
# THE Resource abstraction
using RestInterfaces.Resources: Resource
# Utilities for extracting information from HTTP.Requests
using RestInterfaces.Utils: json_payload, query_parameters

# Mutable state-- a fake backend
name_state = "World"

## `Hello` resource

# Implement the `Resource` interface by subtyping `Resources.Resource` and extending
# `deserialize`, `process`, and `serialize` for an HttpMethod and the
# specialized resource type
struct Hello <: Resource end

# Post

Resources.deserialize(::Post, ::Hello, req) = json_payload(req) |> x -> x[:name]

function Resources.process(::Post, ::Hello, name::String)
  global name_state
  name_state = name
  return nothing
end

Resources.serialize(::Post, ::Hello, ::Nothing) = HTTP.Response(201);

# Get

function Resources.deserialize(::Get, ::Hello, req)
  global name_state
  return query_parameters(req) |> x -> get(x, "name", name_state)
end

function Resources.process(::Get, ::Hello, name::String)
  if name === "Tokyo"
    unprocessable_entity("Cannot greet Tokyo; ignorant of Japanese\n")
  end
  if name === "Tbilisi"
    return "გამარჯობა, თბილისი!"
  end
  return "Hello, $(name)!\n"
end

Resources.serialize(::Get, ::Hello, greeting::String) = HTTP.Response(200, greeting)

## Server

# Extend this method so that the router can match requests to this resource
Resources.path(::Hello) = "/hello"

# Register routes to a router
router = Router(
  "",
  [
    Hello() => Post(), 
    Hello() => Get(),
  ]
)

# Stack middleware and serve on localhost:8081
route(router) |> handle_errors |> HTTP.serve

A client might then see something like...

$ curl -i -X GET "localhost:8081/hello"
HTTP/1.1 200 OK
Transfer-Encoding: chunked

Hello, World!
$ curl -i -X POST --data '{"name":"Boston"}' "localhost:8081/hello"
HTTP/1.1 201 Created
Transfer-Encoding: chunked

$ curl "localhost:8081/hello"
Hello, Boston!
$ curl "localhost:8081/hello?name=Lagos"
Hello, Lagos!
$ curl "localhost:8081/hello?name=Tbilisi"
გამარჯობა, თბილისი!
$ curl -i "localhost:8081/hello?name=Tokyo"
HTTP/1.1 422 Unprocessable Entity
Transfer-Encoding: chunked

422 Unprocessable Entity - Cannot greet Tokyo; ignorant of Japanese
$ curl -i -X DELETE "localhost:8081/hello"
HTTP/1.1 405 Method Not Allowed
Transfer-Encoding: chunked

405 Method Not Allowed - DELETE not allowed for path

Alternatives

  • Mux.jl is a lightweight layer on top of HTTP.jl that can be used to accomplish the same things that RestInterfaces.jl can accomplish. It is more general and more mature than RestInterfaces.jl is, but I am personally not a fan of the callback-style API.