Handling GET/POST requests
Overview
Canisters running on ICP can use HTTP requests in two ways: incoming and outgoing. Incoming HTTP requests refer to HTTP requests that are sent to a canister and can be used to retrieve data from a canister or send new data to the canister. Outgoing HTTP requests refer to HTTP requests that the canister sends to other canisters or external services to retrieve data or send new data.
For outgoing HTTP requests, the HTTPS outcalls feature should be used.
To handle incoming HTTP requests, canisters must define methods for http_requests
and http_requests_update
for GET
and POST
requests respectively.
GET
requests
HTTP GET
requests are used to retrieve and return existing data from an endpoint. To handle a canister's incoming GET
requests, the http_request
method can be exposed. Users and other services can call this method using a query
call. To return HTTP response data, the following examples display how to configure the http_request
to return an HTTP GET
request.
- Motoko
- Rust
- Typescript
- Python
In Motoko, a case
configuration can be used to return different GET
responses based on the endpoint:
public query func http_request(req : HttpRequest) : async HttpResponse {
switch (req.method, not Option.isNull(Array.find(req.headers, isGzip)), req.url) {
case ("GET", false, "/stream") {{
status_code = 200;
headers = [ ("content-type", "text/plain") ];
body = Text.encodeUtf8("Counter");
streaming_strategy = ?#Callback({
callback = http_streaming;
token = {
arbitrary_data = "start";
}
});
upgrade = ?false;
}};
case ("GET", false, _) {{
status_code = 200;
headers = [ ("content-type", "text/plain") ];
body = Text.encodeUtf8("Counter is " # Nat.toText(counter) # "\n" # req.url # "\n");
streaming_strategy = null;
upgrade = null;
}};
case ("GET", true, _) {{
status_code = 200;
headers = [ ("content-type", "text/plain"), ("content-encoding", "gzip") ];
body = "\1f\8b\08\00\98\02\1b\62\00\03\2b\2c\4d\2d\aa\e4\02\00\d6\80\2b\05\06\00\00\00";
streaming_strategy = null;
upgrade = null;
}};
Check out the certified cache example project to see this code in use.
use http::{Request, Response, StatusCode};
#[query]
fn respond_to(req: Request<()>) -> http::Result<Response<()>> {
if req.uri() != "/hello" {
return Response::builder()
.status(StatusCode::NOT_FOUND)
.body(())
}
let response_header = req.headers().contains_key("Hello!");
let body = req.body();
}
Check out the Rust documentation for more info on query calls.
import {
blob,
bool,
Canister,
Func,
nat16,
None,
Opt,
query,
Record,
text,
Tuple,
Variant,
Vec
} from 'azle';
const Token = Record({
// add whatever fields you'd like
arbitrary_data: text
});
const StreamingCallbackHttpResponse = Record({
body: blob,
token: Opt(Token)
});
export const Callback = Func([text], StreamingCallbackHttpResponse, 'query');
const CallbackStrategy = Record({
callback: Callback,
token: Token
});
const StreamingStrategy = Variant({
Callback: CallbackStrategy
});
type HeaderField = [text, text];
const HeaderField = Tuple(text, text);
const HttpResponse = Record({
status_code: nat16,
headers: Vec(HeaderField),
body: blob,
streaming_strategy: Opt(StreamingStrategy),
upgrade: Opt(bool)
});
const HttpRequest = Record({
method: text,
url: text,
headers: Vec(HeaderField),
body: blob,
certificate_version: Opt(nat16)
});
export default Canister({
http_request: query([HttpRequest], HttpResponse, (req) => {
return {
status_code: 200,
headers: [],
body: Buffer.from('hello'),
streaming_strategy: None,
upgrade: None
};
})
});
Check out the Azle documentation for more info on HTTP requests.
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
streaming_strategy: Opt["StreamingStrategy"]
upgrade: Opt[bool]
Header = Tuple[str, str]
class StreamingStrategy(Variant):
Callback: "CallbackStrategy"
class CallbackStrategy(Record):
callback: "Callback"
token: "Token"
Callback = Func(Query[["Token"], "StreamingCallbackHttpResponse"])
class StreamingCallbackHttpResponse(Record):
body: blob
token: Opt["Token"]
class Token(Record):
arbitrary_data: str
@query
def http_request(req: HttpRequest) -> HttpResponse:
return {
"status_code": 200,
"headers": [],
"body": bytes(),
"streaming_strategy": None,
"upgrade": False,
}
Check out the Kybra documentation for more info on HTTP requests.
POST
requests
HTTP POST
requests are used to send data to an endpoint with the intention of retaining that data. To handle incoming POST
requests, the htt_request_update
method can be used. This method uses an update
call, which can be used to change a canister's state. The following examples display how to configure http_request_update
method within your canister.
- Motoko
- Rust
- Typescript
- Python
In Motoko, a case
configuration can be used to return different POST
responses based on the endpoint:
public func http_request_update(req : HttpRequest) : async HttpResponse {
switch (req.method, not Option.isNull(Array.find(req.headers, isGzip))) {
case ("POST", false) {
counter += 1;
{
status_code = 201;
headers = [ ("content-type", "text/plain") ];
body = Text.encodeUtf8("Counter updated to " # Nat.toText(counter) # "\n");
streaming_strategy = null;
upgrade = null;
}
};
case ("POST", true) {
counter += 1;
{
status_code = 201;
headers = [ ("content-type", "text/plain"), ("content-encoding", "gzip") ];
body = "\1f\8b\08\00\37\02\1b\62\00\03\2b\2d\48\49\2c\49\e5\02\00\a8\da\91\6c\07\00\00\00";
streaming_strategy = null;
upgrade = null;
}
};
Check out the certified cache example project to see this code in use.
use http::{Request, Response, StatusCode};
#[update]
fn respond_to(req: Request<()>) -> http::Result<Response<()>> {
let mut builder = Response::builder()
.header("Hello!")
.status(StatusCode::OK);
if req.headers().contains_key("Another-Header") {
builder = builder.header("Another-Header", "Ack");
}
builder.body(())
}
Check out the Rust documentation for more info on update calls.
import {
blob,
bool,
Canister,
Func,
nat16,
None,
Opt,
Record,
text,
Tuple,
update,
Variant,
Vec
} from 'azle';
const Token = Record({
// add whatever fields you'd like
arbitrary_data: text
});
const StreamingCallbackHttpResponse = Record({
body: blob,
token: Opt(Token)
});
export const Callback = Func([text], StreamingCallbackHttpResponse, 'query');
const CallbackStrategy = Record({
callback: Callback,
token: Token
});
const StreamingStrategy = Variant({
Callback: CallbackStrategy
});
type HeaderField = [text, text];
const HeaderField = Tuple(text, text);
const HttpResponse = Record({
status_code: nat16,
headers: Vec(HeaderField),
body: blob,
streaming_strategy: Opt(StreamingStrategy),
upgrade: Opt(bool)
});
const HttpRequest = Record({
method: text,
url: text,
headers: Vec(HeaderField),
body: blob,
certificate_version: Opt(nat16)
});
export default Canister({
http_request_update: update([HttpRequest], HttpResponse, (req) => {
return {
status_code: 200,
headers: [],
body: Buffer.from('hello'),
streaming_strategy: None,
upgrade: None
};
})
});
Check out the Azle documentation for more info on HTTP requests.
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
streaming_strategy: Opt["StreamingStrategy"]
upgrade: Opt[bool]
Header = Tuple[str, str]
class StreamingStrategy(Variant):
Callback: "CallbackStrategy"
class CallbackStrategy(Record):
callback: "Callback"
token: "Token"
Callback = Func(Query[["Token"], "StreamingCallbackHttpResponse"])
class StreamingCallbackHttpResponse(Record):
body: blob
token: Opt["Token"]
class Token(Record):
arbitrary_data: str
@query
def http_request_update(req: HttpRequest) -> HttpResponse:
return {
"status_code": 200,
"headers": [],
"body": bytes(),
"streaming_strategy": None,
"upgrade": False,
}
Check out the Kybra documentation for more info on HTTP requests.