Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-httpclient

Named HTTP clients for Pudu: a client factory, delegating handlers, pooled keep-alive connections, handler lifetimes, logging, and resilience

0.1.1Apache-2.01

InstallClose

Redirect.pudu

Pudu39 lines1.7 KB

GitHub ↗
1/** @Domain.Redirect.Module — which redirects are followed, and how the request changes */2module PuduLangHttpClient.Domain.Redirect34import Std.Http as Http5import PuduLangHttpClient.Domain.Uri as Uri67/** @Domain.Redirect.Hop — the request a followed redirect sends next */8export type Hop = { method: Http.Method, uri: Str, keepsBody: Bool, keepsCredentials: Bool }910/// The statuses that redirect.11const REDIRECTS: Set[Int] = #{300, 301, 302, 303, 307, 308}1213/// The statuses after which a request that is not a `GET` or `HEAD` is sent again as a `GET`.14const TO_GET_AFTER_POST: Set[Int] = #{300, 301, 302}1516/// Whether a status redirects.17export fn isRedirect(code: Int) -> Bool { code in REDIRECTS }1819/// The request that follows a redirect from `from` to `location`, or `None` when it is not followed:20/// the status does not redirect, the location is not an http or https address, or it leaves https21/// for http.22///23/// A `303` turns every method but `HEAD` into `GET`; `300`, `301`, and `302` turn a `POST` into a24/// `GET`; `307` and `308` keep the method and the body. Credentials survive only within one origin.25export fn next(method: &Http.Method, code: Int, from: Str, location: Str) -> Option[Hop] {26  if !isRedirect(code) || location.trim().isEmpty() { return None }27  let destination = Uri.withoutFragment(Uri.resolve(from, Uri.escape(location.trim())))28  let target = Uri.endpointOf(destination) ?29  let origin = Uri.endpointOf(from) ?30  if origin.secure && !target.secure { return None }31  let changed = (code == 303 && *method != Http.Head) || (*method == Http.Post && code in TO_GET_AFTER_POST)32  Some(Hop {33      method: if changed { Http.Get } else { *method },34      uri: destination,35      keepsBody: !changed,36      keepsCredentials: Uri.origin(&origin) == Uri.origin(&target)37    })38}39