Serving an HTTP request
Overview
Canisters can serve or handle an incoming HTTP request using the HTTP Gateway Protocol.
This allows developers to host web applications and APIs from a canister.
How it works
An HTTP request from a client gets intercepted by the HTTP Gateway Protocol, which identifies the target canister and encodes the request in Candid. This encoded request is then sent to the canister for processing. Once processed, the canister replies with an HTTP response. Finally, the HTTP Gateway Protocol decodes the response using Candid and sends it back to the client, completing the communication loop.
For detailed information on how it works, please refer to the HTTP Gateway Protocol specification.
How to make a request
The following example returns 'Hello, World!' in the body at the /hello
endpoint.
- Motoko
- Rust
- TypeScript
- Python
import HashMap = "mo:base/HashMap";
import Blob = "mo:base/Blob";
import Text "mo:base/Text";
import Option "mo:base/Option";
actor {
public type HttpRequest = {
body: Blob;
headers: [HeaderField];
method: Text;
url: Text;
};
public type ChunkId = Nat;
public type SetAssetContentArguments = {
chunk_ids: [ChunkId];
content_encoding: Text;
key: Key;
sha256: ?Blob;
};
public type Path = Text;
public type Key = Text;
public type HttpResponse = {
body: Blob;
headers: [HeaderField];
status_code: Nat16;
};
public type HeaderField = (Text, Text);
private func removeQuery(str: Text): Text {
return Option.unwrap(Text.split(str, #char '?').next());
};
public query func http_request(req: HttpRequest): async (HttpResponse) {
let path = removeQuery(req.url);
if(path == "/hello") {
return {
body = Text.encodeUtf8("root page :" # path);
headers = [];
status_code = 200;
};
};
return {
body = Text.encodeUtf8("404 Not found :" # path);
headers = [];
status_code = 404;
};
};
}
type HeaderField = (String, String);
struct HttpResponse {
status_code: u16,
headers: Vec<HeaderField>,
body: Cow<'static, Bytes>,
}
struct HttpRequest {
method: String,
url: String,
headers: Vec<(String, String)>,
body: ByteBuf,
}
#[query]
fn http_request(req: HttpRequest) -> HttpResponse {
let path = req.url.path();
if path == "/hello" {
HttpResponse {
status_code: 200,
headers: Vec::new(),
body: b"hello, world!".to_vec(),
streaming_strategy: None,
upgrade: None,
}
} else {
HttpResponse {
status_code: 404,
headers: Vec::new(),
body: b"404 Not found :".to_vec(),
streaming_strategy: None,
upgrade: None,
}
}
}
import {
blob,
bool,
Canister,
Func,
nat16,
None,
Opt,
query,
Record,
text,
Tuple,
Variant,
Vec
} from 'azle';
type HeaderField = [text, text];
const HeaderField = Tuple(text, text);
const HttpResponse = Record({
status_code: nat16,
headers: Vec(HeaderField),
body: blob,
});
const HttpRequest = Record({
method: text,
url: text,
headers: Vec(HeaderField),
body: blob,
});
export default class{
@query([HttpRequest], HttpResponse, (req) => {
const path = req.url.pathname;
if(path == "/hello") {
return {
status_code: 200,
headers: [],
body: Buffer.from('hello, world!')
};
} return {
body: Text.encodeUtf8("404 Not found :");
headers: [];
status_code:404;
}
}),
};
from kybra import blob, Func, nat16, Opt, query, Query, Record, Tuple, Variant, Vec
class HttpRequest(Record):
method: str
url: str
headers: Vec["Header"]
body: blob
class HttpResponse(Record):
status_code: nat16
headers: Vec["Header"]
body: blob
Header = Tuple[str, str]
@query
def http_request(req: HttpRequest) -> HttpResponse:
path = req.url.path
if path == "/hello":
return {
"status_code": 200,
"headers": [],
"body": Buffer.from_text("hello, world!").encode("utf-8"),
}
else:
return {
"status_code": 404,
"headers": [],
"body": b"404 Not found :",
}
To learn more about serving an HTTP request in Python, refer to the Kybra book reference on incoming HTTP requests.
Additional examples
The HTTP counter project is an example in Motoko of a 'counter' application that uses the http_request
method to read the current counter value or access some pre-stored data and the http_request_update
method to increment the counter and retrieve the updated value.