http

package
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Nov 30, 2023 License: Apache-2.0, BSD-2-Clause, BSD-3-Clause, + 2 more Imports: 35 Imported by: 38

README

English | 中文

tRPC-Go HTTP protocol

The tRPC-Go framework supports building three types of HTTP-related services:

  1. pan-HTTP standard service (no stub code and IDL file required)
  2. pan-HTTP RPC service (shares the stub code and IDL files used by the RPC protocol)
  3. pan-HTTP RESTful service (provides RESTful API based on IDL and stub code)

The RESTful related documentation is available in /restful

Pan-HTTP standard services

The tRPC-Go framework provides pervasive HTTP standard service capabilities, mainly by adding service registration, service discovery, interceptors and other capabilities to the annotation library HTTP, so that the HTTP protocol can be seamlessly integrated into the tRPC ecosystem

Compared with the tRPC protocol, the pan-HTTP standard service service does not rely on stub code, so the protocol on the service side is named http_no_protocol.

Server-side
configuration writing

Configure the service in the trpc_go.yaml configuration file with protocol http_no_protocol and http2 with http2_no_protocol:

server:
    service: # The service provided by the business service, there can be more than one
      - name: trpc.app.server.stdhttp # The service's route name
        network: tcp # the type of network listening, tcp or udp
        protocol: http_no_protocol # Application layer protocol http_no_protocol
        timeout: 1000 # Maximum request processing time, in milliseconds
        ip: 127.0.0.1
        port: 8080 # Service listening port

Take care to ensure that the configuration file is loaded properly

code writing
single URL registration
import (
    "net/http"

    "trpc.group/trpc-go/trpc-go/codec"
    "trpc.group/trpc-go/trpc-go/log"
    thttp "trpc.group/trpc-go/trpc-go/http"
    trpc "trpc.group/trpc-go/trpc-go"
)

func main() {
    s := trpc.NewServer()
    thttp.HandleFunc("/xxx", handle) 
    // The parameters passed when registering the NoProtocolService must match the service name in the configuration: s.Service("trpc.app.server.stdhttp")
    thttp.RegisterNoProtocolService(s.Service("trpc.app.server.stdhttp")) 
    s.Serve()
}

func handle(w http.ResponseWriter, r *http.Request) error {
    // handle is written in exactly the same way as the standard library HTTP
    // For example, you can read the header in r, etc.
    // You can stream the packet to the client in r.
    clientReq, err := io.ReadAll(r.Body)
    if err ! = nil { /*... */ }
    // Finally use w for packet return
    w.Header().Set("Content-type", "application/text")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("response body"))
    return nil
}
MUX Registration
import (
    "net/http"

    "trpc.group/trpc-go/trpc-go/codec"
    "trpc.group/trpc-go/trpc-go/log"
    thttp "trpc.group/trpc-go/trpc-go/http"
    trpc "trpc.group/trpc-go/trpc-go"
    "github.com/gorilla/mux"
)

func main() {
    s := trpc.NewServer()
    // Routing registration
    router := mux.NewRouter()
    router.HandleFunc("/{dir0}/{dir1}/{day}/{hour}/{vid:[a-z0-9A-Z]+}_{index:[0-9]+}.jpg", handle).
        Methods("GET")
    // The parameters passed when registering RegisterNoProtocolServiceMux must be consistent with the service name in the configuration: s.Service("trpc.app.server.stdhttp")
    thttp.RegisterNoProtocolServiceMux(s.Service("trpc.app.server.stdhttp"), router)
    s.Serve()
}

func handle(w http.ResponseWriter, r *http.Request) error {
    // take the arguments in the url
    vars := mux.Vars(r)
    vid := vars["vid"]
    index := vars["index"]
    log.Infof("vid: %s, index: %s", vid, index)
    return nil
}
Client

This refers to calling a standard HTTP service, which is not necessarily built on the tRPC-Go framework downstream

The cleanest way is actually to use the HTTP Client provided by the standard library directly, but you can't use the service discovery and various plug-in interceptors that provide capabilities (such as monitoring reporting)

configuration writing
client: # backend configuration for client calls
  timeout: 1000 # Maximum processing time for all backend requests
  namespace: Development # environment for all backends
  filter: # List of interceptors before and after all backend function calls
    - simpledebuglog # This is the debug log interceptor, you can add other interceptors, such as monitoring, etc.
  service: # Configuration for a single backend
    - name: trpc.app.server.stdhttp # service name of the downstream http service 
    # # You can use target to select other selector, only service name will be used for service discovery by default (in case of using polaris plugin)
    # target: polaris://trpc.app.server.stdhttp # or ip://127.0.0.1:8080 to specify ip:port for invocation
code writing
package main

import (
    "context"

    trpc "trpc.group/trpc-go/trpc-go"
    "trpc.group/trpc-go/trpc-go/client"
    "trpc.group/trpc-go/trpc-go/codec"
    "trpc.group/trpc-go/trpc-go/http"
    "trpc.group/trpc-go/trpc-go/log"
)

// Data is request message data.
type Data struct {
    Msg string
}

func main() {
    // Omit the tRPC-Go framework configuration loading part, if the following logic is in an RPC handle, 
    // the configuration has generally been loaded normally.
    // Create ClientProxy, set the protocol to HTTP protocol, and serialize it to JSON.
    httpCli := http.NewClientProxy("trpc.app.server.stdhttp",
        client.WithSerializationType(codec.SerializationTypeJSON))
    reqHeader := &http.ClientReqHeader{}
    // Add request field for HTTP Head.
    reqHeader.AddHeader("request", "test")
    rspHead := &http.ClientRspHeader{}
    req := &Data{Msg: "Hello, I am stdhttp client!"}
    rsp := &Data{}
    // Send HTTP POST request.
    if err := httpCli.Post(context.Background(), "/v1/hello", req, rsp,
        client.WithReqHead(reqHeader),
        client.WithRspHead(rspHead),
    ); err != nil {
        log.Warn("get http response err")
        return
    }
    // Get the reply field in the HTTP response header.
    replyHead := rspHead.Response.Header.Get("reply")
    log.Infof("data is %s, request head is %s\n", rsp, replyHead)
}

Pan HTTP RPC Service

Compared to the Pan HTTP Standard Service, the main difference of the Pan HTTP RPC Service is the reuse of the IDL protocol file and its generated stub code, while seamlessly integrating into the tRPC ecosystem (service registration, service routing, service discovery, various plug-in interceptors, etc.)

Note:

In this service form, the HTTP protocol is consistent with the tRPC protocol: when the server returns a failure, the body is empty and the error code error message is placed in the HTTP header

Server-side
configuration writing

First you need to generate the stub code:

trpc create -p helloworld.proto --protocol http -o out

If you are already a tRPC service and want to support the HTTP protocol on the same interface, you don't need to generate the stakes again, just add the http protocol to the configuration

server: # server-side configuration
  service:
    ## The same interface can provide both trpc protocol and http protocol services through two configurations
    - name: trpc.test.helloworld.Greeter # service's route name
      ip: 127.0.0.0 # service listener ip address can use placeholder ${ip},ip or nic, ip is preferred
      port: 80 # The service listens to the port.
      protocol: trpc # Application layer protocol trpc http
    ## Here is the main example, note that the application layer protocol is http
    - name: trpc.test.helloworld.GreeterHTTP # service's route name
      ip: 127.0.0.0 # service listener ip address can use placeholder ${ip},ip or nic, ip is preferred
      port: 80 # The service listens to the port.
      protocol: http # Application layer protocol trpc http
code writing
import (
    "context"
    "fmt"

  trpc "trpc.group/trpc-go/trpc-go"
    "trpc.group/trpc-go/trpc-go/client"
    pb "github.com/xxxx/helloworld/pb"
)

func main() {
    s := trpc.NewServer()
    hello := Hello{}
    pb.RegisterHelloTrpcGoService(s.Service("trpc.test.helloworld.Greeter"), &hello)
    // Same as the normal tRPC service registration
    pb.RegisterHelloTrpcGoService(s.Service("trpc.test.helloworld.GreeterHTTP"), &hello)
    log.Println(s.Serve())
}

type Hello struct {}

// The implementation of the RPC service interface does not need to be aware of the HTTP protocol, it just needs to follow the usual logic to process the request and return a response
func (h *Hello) Hello(ctx context.Context, req *pb.HelloReq) (*pb.HelloRsp, error) {
    fmt.Println("--- got HelloReq", req)
    time.Sleep(time.Second)
    return &pb.HelloRsp{Msg: "Welcome " + req.Name}, nil
}
Custom URL path

Default is /package.service/method, you can customize any URL by alias parameter

  • Protocol definition.
syntax = "proto3";
package trpc.app.server;
option go_package="github.com/your_repo/app/server";

import "trpc.proto";

message Request {
    bytes req = 1;
}

message Reply {
    bytes rsp = 1;
}

service Greeter {
    rpc SayHello(Request) returns (Reply) {
        option (trpc.alias) = "/cgi-bin/module/say_hello";
    };
}
Custom error code handling functions

The default error handling function, which populates the trpc-ret/trpc-func-ret field in the HTTP header, can also be replaced by defining your own ErrorHandler.

import (
    "net/http"

    "trpc.group/trpc-go/trpc-go/errs"
    thttp "trpc.group/trpc-go/trpc-go/http"
)

func init() {
    thttp.DefaultServerCodec.ErrHandler = func(w http.ResponseWriter, r *http.Request, e *errs.Error) {
        // Generally define your own retcode retmsg field, compose the json and write it to the response body
        w.Write([]byte(fmt.Sprintf(`{"retcode":%d, "retmsg":"%s"}`, e.Code, e.Msg)))
        // Each business team can define it in their own git, and the business code can be imported into it
    }
}
Client

There is considerable flexibility in actually calling a pan-HTTP RPC service, as the service provides the HTTP protocol externally, so any HTTP Client can be called, in general, in one of three ways:

  • using the standard library HTTP Client, which constructs the request and parses the response based on the interface documentation provided downstream, with the disadvantage that it does not fit into the tRPC ecosystem (service discovery, plug-in interceptors, etc.)
  • NewStdHTTPClient, which constructs requests and parses responses based on downstream documentation, can be integrated into the tRPC ecosystem, but request responses require documentation to construct and parse.
  • NewClientProxy, using Get/Post/Put interfaces on top of the returned Client, can be integrated into the tRPC ecosystem, and req,rsp strictly conforms to the definition in the IDL protocol file, can reuse the stub code, the disadvantage is the lack of flexibility of the standard library HTTP Client, For example, it is not possible to read back packets in a stream

NewStdHTTPClient is used in the client section of the Pan HTTP Standard Service, and the following describes the stub-based HTTP Client thttp.NewClientProxy.

configuration writing

It is written in the same way as a normal RPC Client, just change the configuration protocol to http:

client:
  namespace: Development # for all backend environments
  filter: # List of interceptors for all backends before and after function calls
  service: # Configuration for a single backend
    - name: trpc.test.helloworld.GreeterHTTP # service name of the backend service
      network: tcp # The network type of the backend service tcp udp
      protocol: http # Application layer protocol trpc http
      # # You can use target to select other selector, only service name will be used by default for service discovery (if Polaris plugin is used)
      # target: ip://127.0.0.1:8000 # request service address
      timeout: 1000 # maximum request processing time
code writing
// Package main is the main package.
package main
import (
    "context"
    "net/http"

    "trpc.group/trpc-go/trpc-go/client"
    thttp "trpc.group/trpc-go/trpc-go/http"
    "trpc.group/trpc-go/trpc-go/log"
    pb "trpc.group/trpc-go/trpc-go/testdata/trpc/helloworld"
)
func main() {
    // omit the configuration loading part of the tRPC-Go framework, if the following logic is in some RPC handle, the configuration is usually already loaded properly
    // Create a ClientProxy, set the protocol to HTTP, serialize it to JSON
    proxy := pb.NewGreeterClientProxy()
    reqHeader := &thttp.ClientReqHeader{}
    // must be left blank or set to "POST"
    reqHeader.Method = "POST"
    // Add request field to HTTP Head
    reqHeader.AddHeader("request", "test")
    // Set a cookie
    cookie := &http.Cookie{Name: "sample", Value: "sample", HttpOnly: false}
    reqHeader.AddHeader("Cookie", cookie.String())
    req := &pb.HelloRequest{Msg: "Hello, I am tRPC-Go client."}
    rspHead := &thttp.ClientRspHeader{}
    // Send HTTP RPC request
    rsp, err := proxy.SayHello(context.Background(), req,
        client.WithReqHead(reqHeader),
        client.WithRspHead(rspHead),
        // Here you can use the code to force the target field in trpc_go.yaml to be overridden to set other selectors, which is generally not necessary, this is just a demonstration of the functionality
        // client.WithTarget("ip://127.0.0.1:8000"),
    )
    if err != nil {
        log.Warn("get http response err")
        return
    }
    // Get the reply field in the HTTP response header
    replyHead := rspHead.Response.Header.Get("reply")
    log.Infof("data is %s, request head is %s\n", rsp, replyHead)
}

FAQ

Enable HTTPS for Client and Server
Mutual Authentication
Configuration Only

Simply add the corresponding configuration items (certificate and private key) in trpc_go.yaml:

server:  # Server configuration
  service:  # Business services provided, can have multiple
    - name: trpc.app.server.stdhttp
      network: tcp
      protocol: http_no_protocol  # Fill in http for generic HTTP RPC services
      tls_cert: "../testdata/server.crt"  # Add certificate path
      tls_key: "../testdata/server.key"  # Add private key path
      ca_cert: "../testdata/ca.pem"  # CA certificate, fill in when mutual authentication is required
client:  # Client configuration
  service:  # Business services provided, can have multiple
    - name: trpc.app.server.stdhttp
      network: tcp
      protocol: http
      tls_cert: "../testdata/server.crt"  # Add certificate path
      tls_key: "../testdata/server.key"  # Add private key path
      ca_cert: "../testdata/ca.pem"  # CA certificate, fill in when mutual authentication is required

No additional TLS/HTTPS-related operations are needed in the code (no need to specify the scheme as https, no need to manually add the WithTLS option, and no need to find a way to include an HTTPS-related identifier in WithTarget or other places).

Code Only

For the server, use server.WithTLS to specify the server certificate, private key, and CA certificate in order:

server.WithTLS(
	"../testdata/server.crt",
	"../testdata/server.key",
	"../testdata/ca.pem",
)

For the client, use client.WithTLS to specify the client certificate, private key, CA certificate, and server name in order:

client.WithTLS(
	"../testdata/client.crt",
	"../testdata/client.key",
	"../testdata/ca.pem",
	"localhost",  // Fill in the server name
)

No additional TLS/HTTPS-related operations are needed in the code.

Example:

func TestHTTPSUseClientVerify(t *testing.T) {
	const (
		network = "tcp"
		address = "127.0.0.1:0"
	)
	ln, err := net.Listen(network, address)
	require.Nil(t, err)
	defer ln.Close()
	serviceName := "trpc.app.server.Service" + t.Name()
	service := server.New(
		server.WithServiceName(serviceName),
		server.WithNetwork("tcp"),
		server.WithProtocol("http_no_protocol"),
		server.WithListener(ln),
		server.WithTLS(
			"../testdata/server.crt",
			"../testdata/server.key",
			"../testdata/ca.pem",
		),
	)
	thttp.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) error {
		w.Write([]byte(t.Name()))
		return nil
	})
	thttp.RegisterNoProtocolService(service)
	s := &server.Server{}
	s.AddService(serviceName, service)
	go s.Serve()
	defer s.Close(nil)
	time.Sleep(100 * time.Millisecond)

	c := thttp.NewClientProxy(
		serviceName,
		client.WithTarget("ip://"+ln.Addr().String()),
	)
	req := &codec.Body{}
	rsp := &codec.Body{}
	require.Nil(t,
		c.Post(context.Background(), "/", req, rsp,
			client.WithCurrentSerializationType(codec.SerializationTypeNoop),
			client.WithSerializationType(codec.SerializationTypeNoop),
			client.WithCurrentCompressType(codec.CompressTypeNoop),
			client.WithTLS(
				"../testdata/client.crt",
				"../testdata/client.key",
				"../testdata/ca.pem",
				"localhost",
			),
		))
	require.Equal(t, []byte(t.Name()), rsp.Data)
}
Client Certificate Not Authenticated
Configuration Only

Simply add the corresponding configuration items (certificate and private key) in trpc_go.yaml:

server:  # Server configuration
  service:  # Business services provided, can have multiple
    - name: trpc.app.server.stdhttp
      network: tcp
      protocol: http_no_protocol  # Fill in http for generic HTTP RPC services
      tls_cert: "../testdata/server.crt"  # Add certificate path
      tls_key: "../testdata/server.key"  # Add private key path
      # ca_cert: ""  # CA certificate, leave empty when the client certificate is not authenticated
client:  # Client configuration
  service:  # Business services provided, can have multiple
    - name: trpc.app.server.stdhttp
      network: tcp
      protocol: http
      # tls_cert: ""  # Certificate path, leave empty when the client certificate is not authenticated
      # tls_key: ""  # Private key path, leave empty when the client certificate is not authenticated
      ca_cert: "none"  # CA certificate, fill in "none" when the client certificate is not authenticated

For the mutual authentication part, the main difference is that the server's ca_cert needs to be left empty and the client's ca_cert needs to be filled with "none".

No additional TLS/HTTPS-related operations are needed in the code (no need to specify the scheme as https, no need to manually add the WithTLS option, and no need to find a way to include an HTTPS-related identifier in WithTarget or other places).

Code Only

For the server, use server.WithTLS to specify the server certificate, private key, and leave the CA certificate empty:

server.WithTLS(
	"../testdata/server.crt",
	"../testdata/server.key",
	"",  // Leave the CA certificate empty when the client certificate is not authenticated
)

For the client, use client.WithTLS to specify the client certificate, private key, and fill in "none" for the CA certificate:

client.WithTLS(
	"",  // Leave the certificate path empty
	"",  // Leave the private key path empty
	"none",  // Fill in "none" for the CA certificate when the client certificate is not authenticated
	"",  // Leave the server name empty
)

No additional TLS/HTTPS-related operations are needed in the code.

Example:

func TestHTTPSSkipClientVerify(t *testing.T) {
	const (
		network = "tcp"
		address = "127.0.0.1:0"
	)
	ln, err := net.Listen(network, address)
	require.Nil(t, err)
	defer ln.Close()
	serviceName := "trpc.app.server.Service" + t.Name()
	service := server.New(
		server.WithServiceName(serviceName),
		server.WithNetwork("tcp"),
		server.WithProtocol("http_no_protocol"),
		server.WithListener(ln),
		server.WithTLS(
			"../testdata/server.crt",
			"../testdata/server.key",
			"",
		),
	)
	thttp.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) error {
		w.Write([]byte(t.Name()))
		return nil
	})
	thttp.RegisterNoProtocolService(service)
	s := &server.Server{}
	s.AddService(serviceName, service)
	go s.Serve()
	defer s.Close(nil)
	time.Sleep(100 * time.Millisecond)

	c := thttp.NewClientProxy(
		serviceName,
		client.WithTarget("ip://"+ln.Addr().String()),
	)
	req := &codec.Body{}
	rsp := &codec.Body{}
	require.Nil(t,
		c.Post(context.Background(), "/", req, rsp,
			client.WithCurrentSerializationType(codec.SerializationTypeNoop),
			client.WithSerializationType(codec.SerializationTypeNoop),
			client.WithCurrentCompressType(codec.CompressTypeNoop),
			client.WithTLS(
				"", "", "none", "",
			),
		))
	require.Equal(t, []byte(t.Name()), rsp.Data)
}
Client uses io.Reader for streaming file upload

Requires trpc-go version >= v0.13.0

The key point is to assign an io.Reader to the thttp.ClientReqHeader.ReqBody field (body is an io.Reader):

reqHeader := &thttp.ClientReqHeader{
	Header:  header,
	ReqBody: body, // Stream send.
}

Then specify client.WithReqHead(reqHeader) when making the call:

c.Post(context.Background(), "/", req, rsp,
	client.WithCurrentSerializationType(codec.SerializationTypeNoop),
	client.WithSerializationType(codec.SerializationTypeNoop),
	client.WithCurrentCompressType(codec.CompressTypeNoop),
	client.WithReqHead(reqHeader),
)

Here's an example:

func TestHTTPStreamFileUpload(t *testing.T) {
	// Start server.
	const (
		network = "tcp"
		address = "127.0.0.1:0"
	)
	ln, err := net.Listen(network, address)
	require.Nil(t, err)
	defer ln.Close()
	go http.Serve(ln, &fileHandler{})
	// Start client.
	c := thttp.NewClientProxy(
		"trpc.app.server.Service_http",
		client.WithTarget("ip://"+ln.Addr().String()),
	)
	// Open and read file.
	fileDir, err := os.Getwd()
	require.Nil(t, err)
	fileName := "README.md"
	filePath := path.Join(fileDir, fileName)
	file, err := os.Open(filePath)
	require.Nil(t, err)
	defer file.Close()
	// Construct multipart form file.
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile("field_name", filepath.Base(file.Name()))
	require.Nil(t, err)
	io.Copy(part, file)
	require.Nil(t, writer.Close())
	// Add multipart form data header.
	header := http.Header{}
	header.Add("Content-Type", writer.FormDataContentType())
	reqHeader := &thttp.ClientReqHeader{
		Header: header,
		ReqBody: body, // Stream send.
	}
	req := &codec.Body{}
	rsp := &codec.Body{}
	// Upload file.
	require.Nil(t,
		c.Post(context.Background(), "/", req, rsp,
			client.WithCurrentSerializationType(codec.SerializationTypeNoop),
			client.WithSerializationType(codec.SerializationTypeNoop),
			client.WithCurrentCompressType(codec.CompressTypeNoop),
			client.WithReqHead(reqHeader),
		))
	require.Equal(t, []byte(fileName), rsp.Data)
}

type fileHandler struct{}

func (*fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	_, h, err := r.FormFile("field_name")
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	w.WriteHeader(http.StatusOK)
	// Write back file name.
	w.Write([]byte(h.Filename))
	return
}
Reading Response Body Stream Using io.Reader in the Client

Requires trpc-go version >= v0.13.0

The key is to add thttp.ClientRspHeader and specify the thttp.ClientRspHeader.ManualReadBody field as true:

rspHead := &thttp.ClientRspHeader{
	ManualReadBody: true,
}

Then, when making the call, add client.WithRspHead(rspHead):

c.Post(context.Background(), "/", req, rsp,
	client.WithCurrentSerializationType(codec.SerializationTypeNoop),
	client.WithSerializationType(codec.SerializationTypeNoop),
	client.WithCurrentCompressType(codec.CompressTypeNoop),
	client.WithRspHead(rspHead),
)

Finally, you can perform streaming reads on rspHead.Response.Body:

body := rspHead.Response.Body // Do stream reads directly from rspHead.Response.Body.
defer body.Close()            // Do remember to close the body.
bs, err := io.ReadAll(body)

Here's an example:

func TestHTTPStreamRead(t *testing.T) {
	// Start server.
	const (
		network = "tcp"
		address = "127.0.0.1:0"
	)
	ln, err := net.Listen(network, address)
	require.Nil(t, err)
	defer ln.Close()
	go http.Serve(ln, &fileServer{})

	// Start client.
	c := thttp.NewClientProxy(
		"trpc.app.server.Service_http",
		client.WithTarget("ip://"+ln.Addr().String()),
	)

	// Enable manual body reading in order to
	// disable the framework's automatic body reading capability,
	// so that users can manually do their own client-side streaming reads.
	rspHead := &thttp.ClientRspHeader{
		ManualReadBody: true,
	}
	req := &codec.Body{}
	rsp := &codec.Body{}
	require.Nil(t,
		c.Post(context.Background(), "/", req, rsp,
			client.WithCurrentSerializationType(codec.SerializationTypeNoop),
			client.WithSerializationType(codec.SerializationTypeNoop),
			client.WithCurrentCompressType(codec.CompressTypeNoop),
			client.WithRspHead(rspHead),
		))
	require.Nil(t, rsp.Data)
	body := rspHead.Response.Body // Do stream reads directly from rspHead.Response.Body.
	defer body.Close()            // Do remember to close the body.
	bs, err := io.ReadAll(body)
	require.Nil(t, err)
	require.NotNil(t, bs)
}

type fileServer struct{}

func (*fileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "./README.md")
	return
}
Client and Server Sending and Receiving HTTP Chunked
  1. Client sends HTTP chunked:
    1. Add chunked Transfer-Encoding header.
    2. Use io.Reader to send the data.
  2. Client receives HTTP chunked: The Go standard library's HTTP automatically supports handling chunked responses. The upper-level user is unaware of it and only needs to loop over reading from resp.Body until io.EOF (or use io.ReadAll).
  3. Server reads HTTP chunked: Similar to client reading.
  4. Server sends HTTP chunked: Assert http.ResponseWriter as http.Flusher, then call flusher.Flush() after sending a portion of the data. This will automatically trigger the chunked encoding and send a chunk.

Here is an example:

func TestHTTPSendReceiveChunk(t *testing.T) {
	// HTTP chunked example:
	//   1. Client sends chunks: Add "chunked" transfer encoding header, and use io.Reader as body.
	//   2. Client reads chunks: The Go/net/http automatically handles the chunked reading.
	//                           Users can simply read resp.Body in a loop until io.EOF.
	//   3. Server reads chunks: Similar to client reads chunks.
	//   4. Server sends chunks: Assert http.ResponseWriter as http.Flusher, call flusher.Flush() after
	//         writing a part of data, it will automatically trigger "chunked" encoding to send a chunk.

	// Start server.
	const (
		network = "tcp"
		address = "127.0.0.1:0"
	)
	ln, err := net.Listen(network, address)
	require.Nil(t, err)
	defer ln.Close()
	go http.Serve(ln, &chunkedServer{})

	// Start client.
	c := thttp.NewClientProxy(
		"trpc.app.server.Service_http",
		client.WithTarget("ip://"+ln.Addr().String()),
	)

	// Open and read file.
	fileDir, err := os.Getwd()
	require.Nil(t, err)
	fileName := "README.md"
	filePath := path.Join(fileDir, fileName)
	file, err := os.Open(filePath)
	require.Nil(t, err)
	defer file.Close()

	// 1. Client sends chunks.

	// Add request headers.
	header := http.Header{}
	header.Add("Content-Type", "text/plain")
	// Add chunked transfer encoding header.
	header.Add("Transfer-Encoding", "chunked")
	reqHead := &thttp.ClientReqHeader{
		Header:  header,
		ReqBody: file, // Stream send (for chunks).
	}

	// Enable manual body reading in order to
	// disable the framework's automatic body reading capability,
	// so that users can manually do their own client-side streaming reads.
	rspHead := &thttp.ClientRspHeader{
		ManualReadBody: true,
	}
	req := &codec.Body{}
	rsp := &codec.Body{}
	require.Nil(t,
		c.Post(context.Background(), "/", req, rsp,
			client.WithCurrentSerializationType(codec.SerializationTypeNoop),
			client.WithSerializationType(codec.SerializationTypeNoop),
			client.WithCurrentCompressType(codec.CompressTypeNoop),
			client.WithReqHead(reqHead),
			client.WithRspHead(rspHead),
		))
	require.Nil(t, rsp.Data)

	// 2. Client reads chunks.

	// Do stream reads directly from rspHead.Response.Body.
	body := rspHead.Response.Body
	defer body.Close() // Do remember to close the body.
	buf := make([]byte, 4096)
	var idx int
	for {
		n, err := body.Read(buf)
		if err == io.EOF {
			t.Logf("reached io.EOF\n")
			break
		}
		t.Logf("read chunk %d of length %d: %q\n", idx, n, buf[:n])
		idx++
	}
}

type chunkedServer struct{}

func (*chunkedServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 3. Server reads chunks.

	// io.ReadAll will read until io.EOF.
	// Go/net/http will automatically handle chunked body reads.
	bs, err := io.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(fmt.Sprintf("io.ReadAll err: %+v", err)))
		return
	}

	// 4. Server sends chunks.

	// Send HTTP chunks using http.Flusher.
	// Reference: https://stackoverflow.com/questions/26769626/send-a-chunked-http-response-from-a-go-server.
	// The "Transfer-Encoding" header will be handled by the writer implicitly, so no need to set it.
	flusher, ok := w.(http.Flusher)
	if !ok {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("expected http.ResponseWriter to be an http.Flusher"))
		return
	}
	chunks := 10
	chunkSize := (len(bs) + chunks - 1) / chunks
	for i := 0; i < chunks; i++ {
		start := i * chunkSize
		end := (i + 1) * chunkSize
		if end > len(bs) {
			end = len(bs)
		}
		w.Write(bs[start:end])
		flusher.Flush() // Trigger "chunked" encoding and send a chunk.
		time.Sleep(500 * time.Millisecond)
	}
	return
}

Documentation

Overview

Package http provides support for http protocol by default, provides rpc server with http protocol, and provides rpc client for calling http protocol.

Index

Constants

View Source
const (
	TrpcVersion     = "trpc-version"
	TrpcCallType    = "trpc-call-type"
	TrpcMessageType = "trpc-message-type"
	TrpcRequestID   = "trpc-request-id"
	TrpcTimeout     = "trpc-timeout"
	TrpcCaller      = "trpc-caller"
	TrpcCallee      = "trpc-callee"
	TrpcTransInfo   = "trpc-trans-info"
	TrpcEnv         = "trpc-env"
	TrpcDyeingKey   = "trpc-dyeing-key"
	// TrpcErrorMessage used to pass error messages,
	// contains user code's error or frame errors (such as validation framework).
	TrpcErrorMessage = "trpc-error-msg"
	// TrpcFrameworkErrorCode used to pass the error code reported by framework.
	TrpcFrameworkErrorCode = "trpc-ret"
	// TrpcUserFuncErrorCode used to pass the error code reported by user.
	TrpcUserFuncErrorCode = "trpc-func-ret"
	// Connection is used to set whether connect mode is "Connection".
	Connection = "Connection"
)

Constants of header keys related to trpc.

View Source
const (
	// ContextKeyHeader key of http header
	ContextKeyHeader = ContextKey("TRPC_SERVER_HTTP_HEADER")
	// ParseMultipartFormMaxMemory maximum memory for parsing request body, default is 32M.
	ParseMultipartFormMaxMemory int64 = 32 << 20
)

Variables

View Source
var (
	// DefaultClientCodec is the default http client codec.
	DefaultClientCodec = &ClientCodec{}

	// DefaultServerCodec is the default http server codec.
	DefaultServerCodec = &ServerCodec{
		AutoGenTrpcHead:              true,
		ErrHandler:                   defaultErrHandler,
		RspHandler:                   defaultRspHandler,
		AutoReadBody:                 true,
		DisableEncodeTransInfoBase64: false,
	}

	// DefaultNoProtocolServerCodec is the default http no protocol server codec.
	DefaultNoProtocolServerCodec = &ServerCodec{
		AutoGenTrpcHead:              true,
		ErrHandler:                   defaultErrHandler,
		RspHandler:                   defaultRspHandler,
		AutoReadBody:                 false,
		DisableEncodeTransInfoBase64: false,
	}
)
View Source
var (
	// DefaultRESTServerTransport is the default RESTful ServerTransport.
	DefaultRESTServerTransport = NewRESTServerTransport(false, transport.WithReusePort(true))

	// DefaultRESTHeaderMatcher is the default REST HeaderMatcher.
	DefaultRESTHeaderMatcher = func(ctx context.Context,
		_ http.ResponseWriter,
		r *http.Request,
		serviceName, methodName string,
	) (context.Context, error) {
		return putRESTMsgInCtx(ctx, r.Header.Get, serviceName, methodName)
	}

	// DefaultRESTFastHTTPHeaderMatcher is the default REST FastHTTPHeaderMatcher.
	DefaultRESTFastHTTPHeaderMatcher = func(
		ctx context.Context,
		requestCtx *fasthttp.RequestCtx,
		serviceName, methodName string,
	) (context.Context, error) {
		headerGetter := func(k string) string {
			return string(requestCtx.Request.Header.Peek(k))
		}
		return putRESTMsgInCtx(ctx, headerGetter, serviceName, methodName)
	}
)
View Source
var DefaultClientTransport = NewClientTransport(false)

DefaultClientTransport default client http transport.

View Source
var DefaultHTTP2ClientTransport = NewClientTransport(true)

DefaultHTTP2ClientTransport default client http2 transport.

View Source
var DefaultHTTP2ServerTransport transport.ServerTransport

DefaultHTTP2ServerTransport is the default server http2 transport.

View Source
var DefaultServerTransport transport.ServerTransport

DefaultServerTransport is the default server http transport.

View Source
var ErrEncodeMissingHeader = errors.New("trpc/http: server encode missing http header in context")

ErrEncodeMissingHeader defines error used for special handling in transport when ctx lost header information.

ErrsToHTTPStatus maps from framework errs retcode to http status code.

View Source
var (

	// FormDataMarshalType the serialization method of the response data,
	// default is json serialization.
	FormDataMarshalType = codec.SerializationTypeJSON
)
View Source
var NewClientProxy = func(name string, opts ...client.Option) Client {
	c := &cli{
		serviceName: name,
		client:      client.DefaultClient,
	}
	c.opts = make([]client.Option, 0, len(opts)+1)
	c.opts = append(c.opts, client.WithProtocol("http"))
	c.opts = append(c.opts, opts...)
	return c
}

NewClientProxy creates a new http backend request proxy. Parameter name means the name of backend http service (e.g. trpc.http.xxx.xxx), mainly used for metrics, can be freely defined but format needs to follow "trpc.app.server.service".

View Source
var NewRoundTripper = newValueDetachedTransport

NewRoundTripper creates new NewRoundTripper and can be replaced.

View Source
var ServiceDesc = server.ServiceDesc{
	HandlerType: nil,
}

ServiceDesc is descriptor for server.RegisterService.

View Source
var StdHTTPTransport = &stdhttp.Transport{
	Proxy: stdhttp.ProxyFromEnvironment,
	DialContext: (&net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
		DualStack: true,
	}).DialContext,
	ForceAttemptHTTP2:     true,
	IdleConnTimeout:       50 * time.Second,
	TLSHandshakeTimeout:   10 * time.Second,
	MaxIdleConnsPerHost:   100,
	DisableCompression:    true,
	ExpectContinueTimeout: time.Second,
}

StdHTTPTransport all RoundTripper object used by http and https.

Functions

func Handle

func Handle(pattern string, h stdhttp.Handler)

Handle registers http handler with custom route.

func HandleFunc

func HandleFunc(pattern string, handler func(w stdhttp.ResponseWriter, r *stdhttp.Request) error)

HandleFunc registers http handler with custom route.

func NewClientTransport

func NewClientTransport(http2Only bool, opt ...transport.ClientTransportOption) transport.ClientTransport

NewClientTransport creates http transport.

func NewFormDataSerialization

func NewFormDataSerialization(tag string) codec.Serializer

NewFormDataSerialization initializes from serialized object.

func NewFormSerialization

func NewFormSerialization(tag string) codec.Serializer

NewFormSerialization initializes the form serialized object.

func NewGetSerialization

func NewGetSerialization(tag string) codec.Serializer

NewGetSerialization initializes the get serialized object.

func NewRESTServerTransport

func NewRESTServerTransport(basedOnFastHTTP bool, opt ...transport.ServerTransportOption) transport.ServerTransport

NewRESTServerTransport creates a RESTful ServerTransport.

func NewStdHTTPClient

func NewStdHTTPClient(name string, opts ...client.Option) *http.Client

NewStdHTTPClient returns http.Client of the go sdk, which is convenient for third-party clients to use, and can report monitoring metrics.

func RegisterContentEncoding

func RegisterContentEncoding(httpContentEncoding string, compressType int)

RegisterContentEncoding registers an existing decompression method, such as RegisterContentEncoding("gzip", codec.CompressTypeGzip).

func RegisterContentType

func RegisterContentType(httpContentType string, serializationType int)

RegisterContentType registers existing serialization method to contentTypeSerializationType and serializationTypeContentType.

func RegisterDefaultService

func RegisterDefaultService(s server.Service)

RegisterDefaultService registers service. See http/README.md for usage details. Deprecated: use RegisterNoProtocolService(s.Service("your.stdhttp.service.name")) instead.

func RegisterNoProtocolService

func RegisterNoProtocolService(s server.Service)

RegisterNoProtocolService registers no protocol service. See http/README.md for usage details.

func RegisterNoProtocolServiceMux

func RegisterNoProtocolServiceMux(s server.Service, mux stdhttp.Handler)

RegisterNoProtocolServiceMux registers service with http standard mux handler. Business registers routing plug-in by himself.

func RegisterSerializer

func RegisterSerializer(httpContentType string, serializationType int, serializer codec.Serializer)

RegisterSerializer registers a new custom serialization method, such as RegisterSerializer("text/plain", 130, xxxSerializer).

func RegisterServiceMux

func RegisterServiceMux(s server.Service, mux stdhttp.Handler)

RegisterServiceMux registers service with http standard mux handler. Business registers routing plug-in by himself. Deprecated: use RegisterNoProtocolServiceMux(s.Service("your.stdhttp.service.name"), mux) instead.

func RegisterStatus

func RegisterStatus[T errs.ErrCode](code T, httpStatus int)

RegisterStatus registers trpc return code to http status.

func Request

func Request(ctx context.Context) *http.Request

Request gets the corresponding http request from context.

func Response

func Response(ctx context.Context) http.ResponseWriter

Response gets the corresponding http response from context.

func SetContentType

func SetContentType(httpContentType string, serializationType int)

SetContentType sets one-way mapping relationship for compatibility with old framework services, allowing multiple http content type to map to the save trpc serialization type. Tell the framework which serialization method to use to parse this content-type. Such as, a non-standard http server returns content type seems to be "text/html", but it is actually "json" data. At this time, you can set it like this: SetContentType("text/html", codec.SerializationTypeJSON).

func WithHeader

func WithHeader(ctx context.Context, value *Header) context.Context

WithHeader sets http header in context.

Types

type Client

type Client interface {
	Get(ctx context.Context, path string, rspBody interface{}, opts ...client.Option) error
	Post(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, opts ...client.Option) error
	Put(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, opts ...client.Option) error
	Patch(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, opts ...client.Option) error
	Delete(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, opts ...client.Option) error
}

Client provides the HTTP client interface. The primary use of this interface is to request standard HTTP services, if you wish to request HTTP RPC services use the client provided by the stub code (simply specify the protocol as "http").

type ClientCodec

type ClientCodec struct{}

ClientCodec decodes http client request.

func (*ClientCodec) Decode

func (c *ClientCodec) Decode(msg codec.Msg, _ []byte) ([]byte, error)

Decode parses metadata in http client's response.

func (*ClientCodec) Encode

func (c *ClientCodec) Encode(msg codec.Msg, reqBody []byte) ([]byte, error)

Encode sets metadata requested by http client. Client has been serialized and passed to reqBody with compress.

type ClientReqHeader

type ClientReqHeader struct {
	// Schema should be named as scheme according to https://www.rfc-editor.org/rfc/rfc3986#section-3.
	// Now that it has been exported, we can do nothing more than add a comment here.
	Schema  string // Examples: HTTP, HTTPS.
	Method  string
	Host    string
	Request *http.Request
	Header  http.Header
	ReqBody io.Reader
}

ClientReqHeader encapsulates http client context. Setting ClientReqHeader is not allowed when NewClientProxy is waiting for the init of Client. Setting ClientReqHeader is needed for each RPC.

func (*ClientReqHeader) AddHeader

func (h *ClientReqHeader) AddHeader(key string, value string)

AddHeader adds http header.

type ClientRspHeader

type ClientRspHeader struct {
	// ManualReadBody is used to control whether to read http response manually
	// (not read automatically by the framework).
	// Set it to true so that you can read data directly from Response.Body manually.
	// The default value is false.
	ManualReadBody bool
	Response       *http.Response
}

ClientRspHeader encapsulates the context returned by http client request.

type ClientTransport

type ClientTransport struct {
	stdhttp.Client // http client, exposed variables, allow user to customize settings.
	// contains filtered or unexported fields
}

ClientTransport client side http transport.

func (*ClientTransport) RoundTrip

func (ct *ClientTransport) RoundTrip(
	ctx context.Context,
	reqBody []byte,
	callOpts ...transport.RoundTripOption,
) (rspBody []byte, err error)

RoundTrip sends and receives http packets, put http response into ctx, no need to return rspBuf here.

type ContextKey

type ContextKey string

ContextKey defines context key of http.

type ErrorHandler

type ErrorHandler func(w http.ResponseWriter, r *http.Request, e *errs.Error)

ErrorHandler handles error of http server's response. By default, the error code is placed in header, which can be replaced by a specific implementation of user.

type FormDataSerialization

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

FormDataSerialization packages kv structure of http request.

func (*FormDataSerialization) Marshal

func (j *FormDataSerialization) Marshal(body interface{}) ([]byte, error)

Marshal serializes.

func (*FormDataSerialization) Unmarshal

func (j *FormDataSerialization) Unmarshal(in []byte, body interface{}) error

Unmarshal unpacks kv structure.

type FormSerialization

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

FormSerialization packages the kv structure of http get request.

func (*FormSerialization) Marshal

func (j *FormSerialization) Marshal(body interface{}) ([]byte, error)

Marshal packages kv structure.

func (*FormSerialization) Unmarshal

func (j *FormSerialization) Unmarshal(in []byte, body interface{}) error

Unmarshal unpacks kv structure.

type GetSerialization

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

GetSerialization packages kv structure of the http get request.

func (*GetSerialization) Marshal

func (s *GetSerialization) Marshal(body interface{}) ([]byte, error)

Marshal packages kv structure.

func (*GetSerialization) Unmarshal

func (s *GetSerialization) Unmarshal(in []byte, body interface{}) error

Unmarshal unpacks kv structure.

type Header struct {
	ReqBody  []byte
	Request  *http.Request
	Response http.ResponseWriter
}

Header encapsulates http context.

func Head(ctx context.Context) *Header

Head gets the corresponding http header from context.

type OptServerTransport

type OptServerTransport func(*ServerTransport)

OptServerTransport modifies ServerTransport.

func WithEnableH2C

func WithEnableH2C() OptServerTransport

WithEnableH2C returns an OptServerTransport which enables H2C.

func WithReusePort

func WithReusePort() OptServerTransport

WithReusePort returns an OptServerTransport which enables reuse port.

type RESTServerTransport

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

RESTServerTransport is the RESTful ServerTransport.

func (*RESTServerTransport) ListenAndServe

func (st *RESTServerTransport) ListenAndServe(ctx context.Context, opt ...transport.ListenServeOption) error

ListenAndServe implements interface of transport.ServerTransport.

type ResponseHandler

type ResponseHandler func(w http.ResponseWriter, r *http.Request, rspBody []byte) error

ResponseHandler handles data of http server's response. By default, the content is returned directly, which can replaced by a specific implementation of user.

type ServerCodec

type ServerCodec struct {
	// AutoGenTrpcHead converts trpc header automatically.
	// Auto conversion could be enabled by setting http.DefaultServerCodec.AutoGenTrpcHead with true.
	AutoGenTrpcHead bool

	// ErrHandler is error code handle function, which is filled into header by default.
	// Business can set this with http.DefaultServerCodec.ErrHandler = func(rsp, req, err) {}.
	ErrHandler ErrorHandler

	// RspHandler returns the data handle function. By default, data is returned directly.
	// Business can customize this method to shape returned data.
	// Business can set this with http.DefaultServerCodec.RspHandler = func(rsp, req, rspBody) {}.
	RspHandler ResponseHandler

	// AutoReadBody reads http request body automatically.
	AutoReadBody bool

	// DisableEncodeTransInfoBase64 indicates whether to disable encoding the transinfo value by base64.
	DisableEncodeTransInfoBase64 bool
}

ServerCodec is the encoder/decoder for HTTP server.

func (*ServerCodec) Decode

func (sc *ServerCodec) Decode(msg codec.Msg, _ []byte) ([]byte, error)

Decode decodes http header. http server transport has filled all the data of request into ctx, and reqBuf here is empty.

func (*ServerCodec) Encode

func (sc *ServerCodec) Encode(msg codec.Msg, rspBody []byte) (b []byte, err error)

Encode sets http header. The buffer of the returned packet has been written to the response writer in header, no need to return rspBuf.

type ServerTransport

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

ServerTransport is the http transport layer.

func NewServerTransport

func NewServerTransport(
	newStdHttpServer func() *stdhttp.Server,
	opts ...OptServerTransport,
) *ServerTransport

NewServerTransport creates a new ServerTransport which implement transport.ServerTransport. The parameter newStdHttpServer is used to create the underlying stdhttp.Server when ListenAndServe, and that server is modified by opts of this function and ListenAndServe.

func (*ServerTransport) ListenAndServe

func (t *ServerTransport) ListenAndServe(ctx context.Context, opt ...transport.ListenServeOption) error

ListenAndServe handles configuration.

Directories

Path Synopsis
Package mockhttp is a generated GoMock package.
Package mockhttp is a generated GoMock package.

Jump to

Keyboard shortcuts

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