Examples
Some examples that may prove potentially useful for those using HTTP.jl
. The code for these examples can also be found on Github in the docs/examples
folder.
Simple Server
A simple example of creating a server with HTTP.jl. It handles creating, deleting, updating, and retrieving Animals from a dictionary thorugh 4 different routes
using HTTP
# modified Animal struct to associate with specific user
mutable struct Animal
id::Int
userId::Base.UUID
type::String
name::String
end
# use a plain `Dict` as a "data store"
const ANIMALS = Dict{Int, Animal}()
const NEXT_ID = Ref(0)
function getNextId()
id = NEXT_ID[]
NEXT_ID[] += 1
return id
end
# "service" functions to actually do the work
function createAnimal(req::HTTP.Request)
animal = JSON2.read(IOBuffer(HTTP.payload(req)), Animal)
animal.id = getNextId()
ANIMALS[animal.id] = animal
return HTTP.Response(200, JSON2.write(animal))
end
function getAnimal(req::HTTP.Request)
animalId = HTTP.URIs.splitpath(req.target)[5] # /api/zoo/v1/animals/10, get 10
animal = ANIMALS[parse(Int, animalId)]
return HTTP.Response(200, JSON2.write(animal))
end
function updateAnimal(req::HTTP.Request)
animal = JSON2.read(IOBuffer(HTTP.payload(req)), Animal)
ANIMALS[animal.id] = animal
return HTTP.Response(200, JSON2.write(animal))
end
function deleteAnimal(req::HTTP.Request)
animalId = HTTP.URIs.splitpath(req.target)[5] # /api/zoo/v1/animals/10, get 10
delete!(ANIMALS, parse(Int, animal.id))
return HTTP.Response(200)
end
# define REST endpoints to dispatch to "service" functions
const ANIMAL_ROUTER = HTTP.Router()
HTTP.@register(ANIMAL_ROUTER, "POST", "/api/zoo/v1/animals", createAnimal)
# note the use of `*` to capture the path segment "variable" animal id
HTTP.@register(ANIMAL_ROUTER, "GET", "/api/zoo/v1/animals/*", getAnimal)
HTTP.@register(ANIMAL_ROUTER, "PUT", "/api/zoo/v1/animals", updateAnimal)
HTTP.@register(ANIMAL_ROUTER, "DELETE", "/api/zoo/v1/animals/*", deleteAnimal)
HTTP.serve(ANIMAL_ROUTER, ip"127.0.0.1", 8080)
Cors Server
Server example that takes after the simple server, however, handles dealing with CORS preflight headers when dealing with more than just a simple request
using HTTP
# modified Animal struct to associate with specific user
mutable struct Animal
id::Int
userId::Base.UUID
type::String
name::String
end
# use a plain `Dict` as a "data store"
const ANIMALS = Dict{Int, Animal}()
const NEXT_ID = Ref(0)
function getNextId()
id = NEXT_ID[]
NEXT_ID[] += 1
return id
end
#CORS headers that show what kinds of complex requests are allowed to API
headers = [
"Access-Control-Allow-Origin" => "*",
"Access-Control-Allow-Headers" => "*",
"Access-Control-Allow-Methods" => "POST;GET;OPTIONS"
]
#=
JSONHandler minimizes code by automatically converting the request body
to JSON to pass to the other service functions automatically. JSONHandler
recieves the body of the response from the other service funtions and sends
back a success response code
=#
function JSONHandler(req::HTTP.Request)
# first check if there's any request body
body = IOBuffer(HTTP.payload(req))
if eof(body)
# no request body
response_body = handle(ANIMAL_ROUTER, req)
else
# there's a body, so pass it on to the handler we dispatch to
response_body = handle(ANIMAL_ROUTER, req, JSON2.read(body, Animal))
end
return HTTP.Response(200, JSON2.write(response_body))
end
#= CorsHandler: handles preflight request with the OPTIONS flag
If a request was recieved with the correct headers, then a response will be
sent back with a 200 code, if the correct headers were not specified in the request,
then a CORS error will be recieved on the client side
Since each request passes throught the CORS Handler, then if the request is
not a preflight request, it will simply go to the JSONHandler to be passed to the
correct service function =#
function CorsHandler(req)
if HTTP.hasheader(req, "OPTIONS")
return HTTP.Response(200, headers = headers)
else
return JSONHandler(req)
end
# **simplified** "service" functions
function createAnimal(req::HTTP.Request, animal)
animal.id = getNextId()
ANIMALS[animal.id] = animal
return animal
end
function getAnimal(req::HTTP.Request)
animalId = HTTP.URIs.splitpath(req.target)[5] # /api/zoo/v1/animals/10, get 10
return ANIMALS[animalId]
end
function updateAnimal(req::HTTP.Request, animal)
ANIMALS[animal.id] = animal
return animal
end
function deleteAnimal(req::HTTP.Request)
animalId = HTTP.URIs.splitpath(req.target)[5] # /api/zoo/v1/animals/10, get 10
delete!(ANIMALS, animal.id)
return ""
end
# add an additional endpoint for user creation
HTTP.@register(ANIMAL_ROUTER, "POST", "/api/zoo/v1/users", createUser)
# modify service endpoints to have user pass UUID in
HTTP.@register(ANIMAL_ROUTER, "GET", "/api/zoo/v1/users/*/animals/*", getAnimal)
HTTP.@register(ANIMAL_ROUTER, "DELETE", "/api/zoo/v1/users/*/animals/*", deleteAnimal)
HTTP.serve(CorsHandler, ip"127.0.0.1", 8080)
Readme Examples
#CLIENT
#HTTP.request sends a HTTP Request Message and returns a Response Message.
r = HTTP.request("GET", "http://httpbin.org/ip"; verbose=3)
println(r.status)
println(String(r.body))
#HTTP.open sends a HTTP Request Message and opens an IO stream from which the Response can be read.
HTTP.open(:GET, "https://tinyurl.com/bach-cello-suite-1-ogg") do http
open(`vlc -q --play-and-exit --intf dummy -`, "w") do vlc
write(vlc, http)
end
end
#SERVERS
#Using HTTP.Servers.listen:
#The server will start listening on 127.0.0.1:8081 by default.
using HTTP
HTTP.listen() do http::HTTP.Stream
@show http.message
@show HTTP.header(http, "Content-Type")
while !eof(http)
println("body data: ", String(readavailable(http)))
end
HTTP.setstatus(http, 404)
HTTP.setheader(http, "Foo-Header" => "bar")
HTTP.startwrite(http)
write(http, "response body")
write(http, "more response body")
end
#Using HTTP.Handlers.serve:
using HTTP
HTTP.serve() do request::HTTP.Request
@show request
@show request.method
@show HTTP.header(request, "Content-Type")
@show HTTP.payload(request)
try
return HTTP.Response("Hello")
catch e
return HTTP.Response(404, "Error: $e")
end
end
#WebSocket Examples
@async HTTP.WebSockets.listen("127.0.0.1", UInt16(8081)) do ws
while !eof(ws)
data = readavailable(ws)
write(ws, data)
end
end
HTTP.WebSockets.open("ws://127.0.0.1:8081") do ws
write(ws, "Hello")
x = readavailable(ws)
@show x
println(String(x))
end;
x = UInt8[0x48, 0x65, 0x6c, 0x6c, 0x6f]
#Output: Hello
#=Custom HTTP Layer Examples
Notes:
There is no enforcement of a "well-defined" stack, you can insert a layer anywhere in the stack even if it logically does not make sense
When creating a custom layer, you need to create a request(), see below for an example
Custom layers is only implemented with the "low-level" request() calls, not the "convenience" functions such as HTTP.get(), HTTP.put(), etc.
module TestRequest=#
import HTTP: Layer, request, Response
abstract type TestLayer{Next <: Layer} <: Layer{Next} end
export TestLayer, request
function request(::Type{TestLayer{Next}}, io::IO, req, body; kw...)::Response where Next
println("Insert your custom layer logic here!")
return request(Next, io, req, body; kw...)
end
end
using HTTP
using ..TestRequest
custom_stack = insert(stack(), StreamLayer, TestLayer)
result = request(custom_stack, "GET", "https://httpbin.org/ip")
# Insert your custom layer logic here!
# HTTP.Messages.Response:
# """
# HTTP/1.1 200 OK
# Access-Control-Allow-Credentials: true
# Access-Control-Allow-Origin: *
# Content-Type: application/json
# Date: Fri, 30 Aug 2019 14:13:17 GMT
# Referrer-Policy: no-referrer-when-downgrade
# Server: nginx
# X-Content-Type-Options: nosniff
# X-Frame-Options: DENY
# X-XSS-Protection: 1; mode=block
# Content-Length: 45
# Connection: keep-alive
# {
# "origin": "--Redacted--"
# }
# """