httptask

package
v0.3.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 27, 2025 License: MPL-2.0 Imports: 6 Imported by: 1

Documentation

Overview

Package httptask provides some helper to wrap http server and some client job into task.

For client job, timeout is controled by context. the timeout info should be applied to whole task (send request + get response + read body). For example:

resp := GetResp().
	From(Request("GET", "http://example.com")).
	RetryN(3).
	Cached()
buf, err := ReadBody().
	From(resp).
	With(task.Timeout(10 * time.Second)).
	Defer(Consume(GetBody().From(resp))).
	Get(ctx)
Example
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"

	"github.com/raohwork/task"
	"github.com/raohwork/task/action"
)

const Addr = ":32100"

func server() task.Task {
	srv := &http.Server{Addr: Addr}
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		size, err := io.Copy(io.Discard, r.Body)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		json.NewEncoder(w).Encode(map[string]interface{}{
			"size": size,
		})
	})
	mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"size":9}`))
	})
	srv.Handler = mux

	return Server(srv)
}

type APIResp struct {
	Size int64 `json:"size"`
}

func apiResp(_ context.Context, res *http.Response) (ret *APIResp, err error) {
	// refer example of [DecodeWith] for simpler way to handle response body
	defer res.Body.Close()
	defer io.Copy(io.Discard, res.Body)
	var v APIResp
	if err = json.NewDecoder(res.Body).Decode(&v); err == nil {
		ret = &v
	}
	return
}

func main() {
	ctx, cancel := context.WithCancel(context.TODO())
	done := server().Go(ctx)
	defer func() { <-done }()
	defer cancel()

	// this will open and close the file multiple times when retrying (file is
	// closed by http client). this is simpler and has nearly no impact on
	// performance comparing to reading and sending large file over network.
	req := Req(http.MethodPost, "http://127.0.0.1"+Addr).
		From(BodyGenFrom(os.Open).By("testdata/super_large_file"))
	// this will cache the file, preventing from opening again, so you have to
	// close it after retrying.
	// file := BodyGenFrom(os.Open).By("testdata/super_large_file").Cached()
	// req := Request(http.MethodPost, "http://127.0.0.1"+Addr).
	// 	From(body)
	resp := action.Get(apiResp).
		From(GetResp().From(req)).
		With(task.Timeout(time.Second)).
		TimedFailF(task.FixedDur(100 * time.Millisecond)).
		RetryN(3).
		// if you have cached the file, close it here.
		// Defer(file.Do(action.CloseIt).NoErr()).
		Cached()
	actual, err := resp.Get(ctx)
	if err != nil {
		fmt.Println("unexpected error:", err)
		return
	}
	fmt.Println(actual.Size)
}
Output:

9

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddCookie added in v0.3.0

func AddCookie() action.Converter2[*http.Cookie, *http.Request, *http.Request]

AddCookie creates an action.Converter2 to add a cookie to the request. It's suggested to write tour own converter to setup request at once.

func AddHeader added in v0.3.0

AddHeader creates an action.Converter2 to add a header to the request. It's suggested to write tour own converter to setup request at once.

Common usage: request = request.Then(AddHeader("Content-Type").By("text/json"))

func BodyGenBy added in v0.3.0

func BodyGenBy[T io.Reader](f func() (T, error)) action.Data[io.Reader]

BodyGenBy creates an action.Data to generate request body.

f is a function to generate request body, typically a custom function.

func BodyGenFrom added in v0.3.0

func BodyGenFrom[P any, T io.Reader](f func(P) (T, error)) action.Converter[P, io.Reader]

BodyGenFrom creates an action.Converter to generate request body.

f is a function to generate request body from input parameter, like os.Open.

func Consume added in v0.3.0

func Consume(body action.Data[io.ReadCloser]) func()

Consume creates a function to consume the body of a response.

See example in DecodeWith.

func DecodeWith added in v0.3.0

func DecodeWith[T any, D Decoder](f func(io.Reader) D) action.Converter[io.Reader, T]

DecodeWith creates an action.Converter to decode response body.

Example
// for content of server(), see package example
ctx, cancel := context.WithCancel(context.TODO())
done := server().Go(ctx)
defer func() { <-done }()
defer cancel()

req := Request(http.MethodPost, "http://localhost"+Addr+"/get")
resp := GetResp().From(req)
body := GetBody().From(resp)
data, err := DecodeWith[map[string]interface{}](json.NewDecoder).
	From(Reader(body)).
	Defer(Consume(body)).
	Get(ctx)
if err != nil {
	panic(err)
}

fmt.Println(data)
Output:

map[size:9]

func GetBody added in v0.3.0

GetBody creates an action.Converter to get the body of a response.

Extracted body is guaranteed to be non-nil.

func GetResp added in v0.3.0

func GetResp() action.Converter[*http.Request, *http.Response]

GetResp creates an action.Converter to convert client request into response by sending it using http.DefaultClient.

The context is applied to the request before sending.

Common usage: resp, err := GetResp().From(request).Get(ctx)

func ParseWith added in v0.3.0

func ParseWith[T any](f func([]byte, any) error) action.Converter[[]byte, T]

ParseWith creates an action.Converter to parse response body.

Example
// for content of server(), see package example
ctx, cancel := context.WithCancel(context.TODO())
done := server().Go(ctx)
defer func() { <-done }()
defer cancel()

req := Request(http.MethodPost, "http://localhost"+Addr+"/get")
resp := GetResp().From(req)
body := ReadBody().From(resp)
data, err := ParseWith[map[string]interface{}](json.Unmarshal).
	From(body).
	Get(ctx)
if err != nil {
	panic(err)
}

fmt.Println(data)
Output:

map[size:9]

func ReadBody added in v0.3.0

func ReadBody() action.Converter[*http.Response, []byte]

ReadBody creates an action.Converter to read the whole body of a response.

func ReadResp added in v0.3.0

ReadResp creates an action.Converter2 to convert client request into response by sending it using an http client.

The context is applied to the request before sending.

Common usage: resp, err := ReadResp().By(client).From(request).Get(ctx)

func Reader added in v0.3.0

func Reader[T io.Reader](i action.Data[T]) action.Data[io.Reader]

Reader creates an action.Data to help type checking.

See example in DecodeWith.

func Req added in v0.3.0

func Req(method, url string) action.Converter[io.Reader, *http.Request]

Req creates an action.Converter to build http client request.

Request context is set by ReadResp or GetResp.

func Request added in v0.3.0

func Request(method, uri string) action.Data[*http.Request]

Request is like Req, but without body.

func Server

func Server(s *http.Server, shutdownMods ...task.CtxMod) task.Task

Server wraps s into a task so it can shutdown gracefully when canceled.

s.Shutdown is called with a new context modified by shutdownMods.

func SetContentLength added in v0.3.0

func SetContentLength() action.Converter2[int64, *http.Request, *http.Request]

SetContentLength creates a converter to update the request. It's suggested to write tour own converter to setup request at once.

Common usage: request = request.Then(SetContentLength().By(len(body)))

func SetHeader added in v0.3.0

SetHeader creates an action.Converter2 to set a header to the request. It's suggested to write tour own converter to setup request at once.

Common usage: request = request.Then(SetHeader("Content-Type").By("text/json"))

Types

type Decoder added in v0.3.0

type Decoder interface {
	Decode(any) error
}

Decoder abstracts some common decoder like json.Decoder.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL