the first HTTP requests are done

 7th February 2024 at 2:09pm

working with HTTP requests

I started from my scheme friend's code as a reference.

  (define (make-rpc-request scheme host url port username password #!optional (session-id (*session-id*)))
    (make-request
      #:method 'POST
      #:uri (make-uri
              #:scheme scheme
              #:host host
              #:port port
              #:path url
              #:username username
              #:password password)
      #:headers (headers `((x-transmission-session-id ,session-id)))))

I want to produce a very simple request. I don't think I need so much abstraction (which is obviously good, and versatile); I just want things to run.

The #: prefix indicates keyword arguments.

The HTTP request to the API is created (and I managed to test it without disclosing the sensitive data (url, user-id, etc.).

; creates the HTTP request for snatched API request
; -> intarweb#request
(define (make-request-to-snatched)
    (make-request
      #:method 'GET
      #:uri (build-snatched-url (number->string USER-ID) (number->string NBR-OF-SNATCHES))
	  ))

; returns a intarweb#request object
(test #t (request? (make-request-to-snatched)))
; with the proper URL
(test EXPECTED-SNATCHED-URL (request-uri (make-request-to-snatched)))

I thought my intarweb#request could be consumed by chicken's http-client, but some trouble ensued.

Error: (call-with-input-request) The first argument must be either an uri-common object, an intarweb request object, or an URI string
#<intarweb#request>
#f
#<procedure (a5406 p r)>

fixing the HTTP request: creating URI objects

sjamaan, in #chicken@libera.chat, pointed out that the request could havean improper URI object inside. As I built the uri myself using some string-append, this was the case, indeed.

I tried properly assembling the uri using the uri constructor, but despite the progress it was taking a bit too long; I just cheated my way out of it: absolute-uri will construct an intarweb#uri.

; manually creates URI string for snatched API request
; Integer Integer -> String
(define (build-snatched-uri user-id limit)
  (string-append SNATCHED-URL
				 "&id=" user-id
				 "&type=snatched"
				 "&limit=" limit))
(test EXPECTED-SNATCHED-URI
	  (build-snatched-uri (number->string USER-ID) (number->string NBR-OF-SNATCHES)))

; creates URI object for snatched API request
; String -> intarweb#uri
(define (create-snatched-uri-object uri-string)
  (absolute-uri uri-string))
(test #t (uri? (create-snatched-uri-object EXPECTED-SNATCHED-URI)))

With the above, the request is now

; creates the HTTP request for snatched API request
; -> intarweb#request
(define (make-request-to-snatched api-key)
    (make-request
      #:method 'GET
      #:uri (create-snatched-uri-object EXPECTED-SNATCHED-URI)
	  #:headers (headers `((Authorization, api-key)))))
	  
; returns a intarweb#request object
(test #t (request? (make-request-to-snatched API-KEY)))

sending the request with http-client

Then I had to understand how exactly to work with HTTP responses.

For this case, I'm using chicken's http-client module. There's an example here, which I cite below.

(import http-client (chicken io))

(with-input-from-request "http://wiki.call-cc.org/" #f read-string)
 => ;; [the chicken wiki page HTML contents]

I think it's still my uneasiness with the syntax, but I was looking at that code block as if with-input-from-request were a definition; but the => comment finally clicked, and I now realise it is a function call. It's hard to explain — but I definitely see it now.

What I also did not understand so well was the workings of the thunks (lambdas with no arguments). From the documentation of with-input-from-request,

These thunks will be executed with the current input (or output) port to the request or response port, respectively.

With the help of (again!) sjamaan in the chicken IRC channel,

(define (thunk-exposing-input-port)
  (print "Thunk calling with-input-from-request: " (current-input-port)))

; sends the request and gets the response
; -> intarweb#response
(define (get-snatched-response)
  (print "Outside calling with-input-from-request: " (current-input-port))
  (with-input-from-request "https://google.com" #f thunk-exposing-input-port))
  ;(display (with-input-from-request (make-request-to-snatched API-KEY) #f thunk-exposing-input-port)))

(get-snatched-response)

current-input-port and current-output-port are like stdin and stdout, and can be routed to other ports.

That was nice! What I now need is to work this response in its JSON form, to extract a few fields.