|
1 | 1 | import Blob "mo:base/Blob"; |
2 | | -import Nat64 "mo:base/Nat64"; |
3 | 2 | import Text "mo:base/Text"; |
4 | 3 | import IC "ic:aaaaa-aa"; |
5 | 4 |
|
6 | | -//Actor |
7 | 5 | persistent actor { |
8 | 6 |
|
9 | | - //This method sends a GET request to a URL with a free API we can test. |
10 | | - //This method returns Coinbase data on the exchange rate between USD and ICP |
11 | | - //for a certain day. |
12 | | - //The API response looks like this: |
13 | | - // [ |
14 | | - // [ |
15 | | - // 1682978460, <-- start timestamp |
16 | | - // 5.714, <-- lowest price during time range |
17 | | - // 5.718, <-- highest price during range |
18 | | - // 5.714, <-- price at open |
19 | | - // 5.714, <-- price at close |
20 | | - // 243.5678 <-- volume of ICP traded |
21 | | - // ], |
22 | | - // ] |
23 | | - |
24 | | - //function to transform the response |
| 7 | + // #region transform |
| 8 | + // Strip HTTP response headers (date, cookies, tracking IDs) that vary across replicas. |
| 9 | + // In replicated mode, all replicas must see an identical response for consensus to |
| 10 | + // succeed — the transform ensures this by discarding non-deterministic fields. |
25 | 11 | public query func transform({ |
26 | 12 | context : Blob; |
27 | 13 | response : IC.http_request_result; |
28 | 14 | }) : async IC.http_request_result { |
29 | | - { |
30 | | - response with headers = []; // not interested in the headers |
31 | | - }; |
| 15 | + { response with headers = [] }; |
32 | 16 | }; |
33 | | - |
34 | | - public func get_icp_usd_exchange() : async Text { |
35 | | - |
36 | | - //1. SETUP ARGUMENTS FOR HTTP GET request |
37 | | - let ONE_MINUTE : Nat64 = 60; |
38 | | - let start_timestamp : Nat64 = 1682978460; //May 1, 2023 22:01:00 GMT |
39 | | - let host : Text = "api.exchange.coinbase.com"; |
40 | | - let url = "https://" # host # "/products/ICP-USD/candles?start=" # Nat64.toText(start_timestamp) # "&end=" # Nat64.toText(start_timestamp) # "&granularity=" # Nat64.toText(ONE_MINUTE); |
41 | | - |
42 | | - // 1.2 prepare headers for the system http_request call |
43 | | - let request_headers = [ |
44 | | - { name = "User-Agent"; value = "price-feed" }, |
45 | | - ]; |
46 | | - |
47 | | - // 1.3 The HTTP request |
48 | | - let http_request : IC.http_request_args = { |
49 | | - url = url; |
50 | | - max_response_bytes = null; //optional for request |
51 | | - headers = request_headers; |
52 | | - body = null; //optional for request |
| 17 | + // #endregion transform |
| 18 | + |
| 19 | + // #region get_request |
| 20 | + public func send_http_get_request() : async Text { |
| 21 | + let request : IC.http_request_args = { |
| 22 | + url = "https://postman-echo.com/get?greeting=hello-from-icp"; |
| 23 | + // Always set max_response_bytes to a tight bound. The cycle cost scales |
| 24 | + // with this value, not the actual response size. If omitted, the system |
| 25 | + // assumes 2MB. Unused cycles are refunded, but you still pay for the |
| 26 | + // declared maximum. |
| 27 | + max_response_bytes = ?(3_000 : Nat64); |
| 28 | + headers = [{ name = "User-Agent"; value = "ic-canister" }]; |
| 29 | + body = null; |
53 | 30 | method = #get; |
54 | | - transform = ?{ |
55 | | - function = transform; |
56 | | - context = Blob.fromArray([]); |
57 | | - }; |
58 | | - // Toggle this flag to switch between replicated and non-replicated http outcalls. |
59 | | - is_replicated = ?false; |
| 31 | + transform = ?{ function = transform; context = Blob.fromArray([]) }; |
| 32 | + // Replicated mode: all subnet nodes make the request independently, |
| 33 | + // providing strong integrity guarantees via consensus. |
| 34 | + is_replicated = ?true; |
60 | 35 | }; |
61 | 36 |
|
62 | | - //2. MAKE HTTPS REQUEST AND WAIT FOR RESPONSE, BUT MAKE SURE TO ADD CYCLES. |
63 | | - let http_response : IC.http_request_result = await (with cycles = 230_949_972_000) IC.http_request(http_request); |
| 37 | + // Cycles must be explicitly attached to management canister calls. |
| 38 | + // The amount is based on request size and max_response_bytes. |
| 39 | + let response = await (with cycles = 230_949_972_000) IC.http_request(request); |
64 | 40 |
|
65 | | - //3. DECODE THE RESPONSE |
66 | | - |
67 | | - //As per the type declarations, the BODY in the HTTP response |
68 | | - //comes back as Blob. Type signature: |
69 | | - |
70 | | - //public type http_request_result = { |
71 | | - // status : Nat; |
72 | | - // headers : [HttpHeader]; |
73 | | - // body : Blob; |
74 | | - // }; |
75 | | - |
76 | | - //We need to decode that Blob that is the body into readable text. |
77 | | - //To do this, we: |
78 | | - // 1. Use Text.decodeUtf8() method to convert the Blob to a ?Text optional |
79 | | - // 2. We use a switch to explicitly call out both cases of decoding the Blob into ?Text |
80 | | - let decoded_text : Text = switch (Text.decodeUtf8(http_response.body)) { |
81 | | - case (null) { "No value returned" }; |
82 | | - case (?y) { y }; |
| 41 | + // postman-echo.com echoes back the request metadata as JSON, letting you |
| 42 | + // verify the query params and headers were sent correctly. |
| 43 | + switch (Text.decodeUtf8(response.body)) { |
| 44 | + case (?text) text; |
| 45 | + case null "Response is not valid UTF-8"; |
83 | 46 | }; |
84 | | - |
85 | | - //4. RETURN RESPONSE OF THE BODY |
86 | | - //The API response will looks like this: |
87 | | - // |
88 | | - // ("[[1682978460,5.714,5.718,5.714,5.714,243.5678]]") |
89 | | - // |
90 | | - //The API response looks like this: |
91 | | - // [ |
92 | | - // [ |
93 | | - // 1682978460, <-- start timestamp |
94 | | - // 5.714, <-- lowest price during time range |
95 | | - // 5.718, <-- highest price during range |
96 | | - // 5.714, <-- price at open |
97 | | - // 5.714, <-- price at close |
98 | | - // 243.5678 <-- volume of ICP traded |
99 | | - // ], |
100 | | - // ] |
101 | | - decoded_text; |
102 | 47 | }; |
103 | | - |
| 48 | + // #endregion get_request |
104 | 49 | }; |
0 commit comments