gurl

package module
v0.0.0-...-f9ab01d Latest Latest
Warning

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

Go to latest
Published: Nov 20, 2022 License: Apache-2.0 Imports: 12 Imported by: 0

README

gURL: Go URL Request Library Go Reference test gosec trivy

Make a URL request in Go easy.

	var resp struct {
		KeysURL string `json:"keys_url"`
	}

	if err := gurl.Get("https://api.github.com/",
		gurl.WithExpectedCode(200),
		gurl.WithReader(gurl.DecodeAsJSON(&resp)),
	); err != nil {
		log.Fatal(err.Error())
	}

	fmt.Println(resp.KeysURL)
	//Output: https://api.github.com/user/keys

Motivation

Go official net/http package is simple and powerful to control HTTP communication. However we need to write similar code (creating a request, error handling, decoding a response, etc.) in most cases. Major use cases should be able to implement easier like requests in Python.

How-to based Usages

Passing body data

WithBody() and ByReader() simply set io.Reader to read HTTP request body.

	r := bytes.NewReader([]byte("my_test_data"))

	if err := gurl.Post(serverURL,
		gurl.WithBody(gurl.ByReader(r)),
	); err != nil {
		log.Fatal(err.Error())
	}

EncodeAsJSON() supports JSON encoding from struct data to byte data and create a new io.Reader.

	var req struct {
		Name string `json:"name"`
	}

	if err := gurl.Post(serverURL,
		gurl.WithBody(gurl.EncodeAsJSON(req)),
	); err != nil {
		log.Fatal(err.Error())
	}

Also, gurl has EncodeAsURL() to support URL encoding.

	body := map[string]any{
		"color": "blue",
		"text":  "Hello Günter",
	}

	if err := gurl.Post(serverURL,
		// It will send "color=blue&text=Hello+G%C3%BCnter" as request body
		gurl.WithBody(gurl.EncodeAsURL(body)),
	); err != nil {
		log.Fatal(err.Error())
	}
Passing query string parameters in URL

WithParam() option adds query parameter to URL.

	// It will send request to https://www.google.com/search?q=security
	if err := gurl.Get("https://www.google.com/search",
		gurl.WithParam("q", "security"),
	); err != nil {
		log.Fatal(err.Error())
	}

If original URL has query string, WithParam() keeps original query parameters and appends new parameters.

	// It will send request to https://www.google.com/search?q=security&source=hp&sclient=gws-wiz
	if err := gurl.Get("https://www.google.com/search&q=security",
		gurl.WithParam("source", "hp"),
		gurl.WithParam("sclient", "gws-wiz"),
	); err != nil {
		log.Fatal(err.Error())
	}
Custom Headers

WithHeader() can add HTTP header to a request.

	if err := gurl.Get(serverURL,
		gurl.WithHeader("Authorization", "Bearer XXXXXXXX"),
	); err != nil {
		log.Fatal(err.Error())
	}
JSON Response Content

WithReader() and DecodeAsJSON() support to decode JSON formatted response.

	var resp struct {
		KeysURL string `json:"keys_url"`
	}

	if err := gurl.Get("https://api.github.com/",
		gurl.WithReader(gurl.DecodeAsJSON(&resp)),
	); err != nil {
		log.Fatal(err.Error())
	}

	fmt.Println(resp.KeysURL)
	//Output: https://api.github.com/user/keys
Dump response body

CopyStream() calls io.Copy() to read response body and to write given io.Writer. For example, following code saves result of HTTP request to a temp file.

	f, err := os.CreateTemp("", "*.json")
	if err != nil {
		log.Fatal(err.Error())
	}

	if err := gurl.Get("https://api.github.com/",
		gurl.WithReader(gurl.CopyStream(f)),
	); err != nil {
		log.Fatal(err.Error())
	}
With context.Context

WithCtx() will create http.Request with context.Context.

	if err := gurl.Get("https://slow.example.com",
	    gurl.WithCtx(ctx),
	); err != nil {
		log.Fatal(err.Error())
	}
Error handling

gurl uses goerr and keeps response body content to error structure when the request is failed. It can be extracted with errors.As method.

	if err := gurl.Get("https://example.com",
		gurl.WithClient(&errorClient{}),
	); err != nil {
		var goErr *goerr.Error
		if errors.As(err, &goErr) {
			fmt.Println(string(goErr.Values()["body"].([]byte)))
		}
	}
	//Output: crashed!
Testing with your http.Client or http.HandlerFunc

You can use own http.Client that is implemented Do(req *http.Request) (*http.Response, error) method.

type testClient struct{}

func (x *testClient) Do(req *http.Request) (*http.Response, error) {
	fmt.Println(req.URL)
	return &http.Response{
		StatusCode: 200,
		Body:       io.NopCloser(bytes.NewReader([]byte{})),
	}, nil
}

Then, following code will output https://www.google.com/search?q=security.

	client := &testClient{}

	if err := gurl.Get("https://www.google.com/search",
		gurl.WithParam("q", "security"),
		gurl.WithClient(client),
	); err != nil {
		log.Fatal(err.Error())
	}

Also WithHandlerFunc() can be used to test your HTTP server that is implemented ServeHTTP.

type testServer struct{}

func (x testServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Println(r.URL)
	w.WriteHeader(200)
	w.Write([]byte("hello"))
}
	server := &testServer{}

	if err := gurl.Get("https://www.google.com/search",
		gurl.WithParam("q", "security"),
		gurl.WithHandlerFunc(server.ServeHTTP),
	); err != nil {
		log.Fatal(err.Error())
	}

License

Apache License 2.0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrUnexpectedCode = goerr.New("unexpected status code")
	ErrInvalidOption  = goerr.New("invalid option")
)

Functions

func Connect

func Connect(uri string, options ...Option) error

func Delete

func Delete(uri string, options ...Option) error

func Get

func Get(uri string, options ...Option) error
func Head(uri string, options ...Option) error

func Options

func Options(uri string, options ...Option) error

func Patch

func Patch(uri string, options ...Option) error

func Post

func Post(uri string, options ...Option) error

func Put

func Put(uri string, options ...Option) error

func Trace

func Trace(uri string, options ...Option) error

Types

type Client

type Client interface {
	Do(req *http.Request) (*http.Response, error)
}

type Method

type Method int
const (
	MethodGET Method = iota + 1
	MethodHEAD
	MethodPOST
	MethodPUT
	MethodDELETE
	MethodCONNECT
	MethodOPTIONS
	MethodTRACE
	MethodPATCH
)

type Option

type Option func(req *Request)

func WithBody

func WithBody(body RequestBody) Option

WithBody sets body of HTTP request as io.Reader.

func WithClient

func WithClient(client Client) Option

WithClient sets Client interface to handle http.Request.

func WithCtx

func WithCtx(ctx context.Context) Option

WithCtx will set given context.Context to http.Request.

func WithExpectedCode

func WithExpectedCode(statusCode int) Option

WithExpectedCode sets expected status code. If returned status code from server is not matched with *statusCode*, Emit method will return ErrUnexpectedCode. Default value is 200 (HTTP OK). If you don't want to handle status code, set 0 and Emit method will return no error with any status code.

func WithHandlerFunc

func WithHandlerFunc(f http.HandlerFunc) Option

WithHandlerFunc is a option for testing. The f http.HandlerFunc will be injected as Client and record response with httptest.ResponseRecorder.

func WithHeader

func WithHeader(key, value string) Option

WithHeader adds a pair of key and value as HTTP header. This option can be set multiply.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets *slog.Logger as logging interface.

func WithParam

func WithParam(key, value string) Option

WithParam adds a pair of key and value for Query String Parameter. This method will not overwrite existing query string in original URL.

func WithReader

func WithReader(f ResponseReader) Option

WithReader is a handler of response body. It sets a functions to be called with Body of http.Response. The function must call r.Close() to terminate communication explicitly.

type Request

type Request struct {
	// contains filtered or unexported fields
}

func New

func New(method Method, url string, options ...Option) *Request

New creates a new Request with default values and configures Request with options.

func (*Request) Emit

func (x *Request) Emit() error

type RequestBody

type RequestBody func() (io.Reader, string, error)

func ByReader

func ByReader(r io.Reader) RequestBody

func EncodeAsJSON

func EncodeAsJSON(data any) RequestBody

EncodeAsJSON will send body as JSON format and add Content-Type header with "application/json" automatically if Content-Type does not exist.

func EncodeAsURL

func EncodeAsURL(data map[string]any) RequestBody

EncodeAsURL will sends body as URL encoded format and add Content-Type header with "application/x-www-form-urlencoded" automatically if Content-Type does not exist. A value will be converted to string with %v of fmt.Sprintf.

type ResponseReader

type ResponseReader func(r io.ReadCloser) error

func CopyStream

func CopyStream(w io.Writer) ResponseReader

func DecodeAsJSON

func DecodeAsJSON(out any) ResponseReader

Jump to

Keyboard shortcuts

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