github.com/google/go-safeweb@v0.0.0-20231219055052-64d8cfc90fbb/internal/requesttesting/test_harness.go (about)

     1  // Copyright 2020 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package requesttesting provides a harness and other test utilities for
    16  // verifying the behaviour of the net/http package in Go's standard library.
    17  package requesttesting
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"io"
    23  	"net"
    24  	"net/http"
    25  	"sync"
    26  )
    27  
    28  // AssertHandler is used to assert properties about the http.Request that it receives in using a callback function.
    29  type AssertHandler struct {
    30  	callback func(*http.Request)
    31  }
    32  
    33  func (h *AssertHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    34  	h.callback(r)
    35  	if _, err := io.WriteString(w, "Hello world!"); err != nil {
    36  		panic(err)
    37  	}
    38  }
    39  
    40  // FakeListener creates a custom listener that avoids opening a socket in order
    41  // to establish communication between an HTTP client and server
    42  type FakeListener struct {
    43  	closeOnce      sync.Once
    44  	channel        chan net.Conn
    45  	serverEndpoint io.Closer
    46  	clientEndpoint net.Conn
    47  }
    48  
    49  // NewFakeListener creates an instance of fakeListener.
    50  func NewFakeListener() *FakeListener {
    51  	s2c, c2s := net.Pipe()
    52  	c := make(chan net.Conn, 1)
    53  	c <- s2c
    54  	return &FakeListener{
    55  		channel:        c,
    56  		serverEndpoint: s2c,
    57  		clientEndpoint: c2s,
    58  	}
    59  }
    60  
    61  // Accept passes a network connection to the HTTP server to enable bidirectional communication with the client.
    62  // It will return an error if Accept is called after the listener was closed.
    63  func (l *FakeListener) Accept() (net.Conn, error) {
    64  	ch, ok := <-l.channel
    65  	if !ok {
    66  		return nil, errors.New("Listener closed")
    67  	}
    68  	return ch, nil
    69  }
    70  
    71  // Close will close the two network connections and the listener.
    72  func (l *FakeListener) Close() error {
    73  	l.closeOnce.Do(func() {
    74  		close(l.channel)
    75  	})
    76  	err := l.serverEndpoint.Close()
    77  	err2 := l.clientEndpoint.Close()
    78  	if err2 != nil {
    79  		return err2
    80  	}
    81  	return err
    82  }
    83  
    84  // Addr returns the network address of the client endpoint.
    85  func (l *FakeListener) Addr() net.Addr {
    86  	return l.clientEndpoint.LocalAddr()
    87  }
    88  
    89  // SendRequest writes a request to the client endpoint connection. This will be passed to the server through the listener.
    90  // The function blocks until the server has finished reading the message.
    91  func (l *FakeListener) SendRequest(request []byte) error {
    92  	n, err := l.clientEndpoint.Write(request)
    93  
    94  	if err != nil {
    95  		return err
    96  	}
    97  	if n != len(request) {
    98  		return errors.New("client connection failed to write the entire request")
    99  	}
   100  	return nil
   101  }
   102  
   103  // ReadResponse reads the response from the clientEndpoint connection, sent by the listening server.
   104  // It will block until the server has sent a response.
   105  func (l *FakeListener) ReadResponse(bytes []byte) (int, error) {
   106  	return l.clientEndpoint.Read(bytes)
   107  }
   108  
   109  // MakeRequest instantiates a new http.Server, sends the request provided as
   110  // argument and returns the response. callback will be called in the
   111  // http.Handler with the http.Request that the handler receives. The size of the
   112  // response is limited to 4096 bytes. If the response received is larger, an
   113  // error will be returned.
   114  func MakeRequest(ctx context.Context, req []byte, callback func(*http.Request)) ([]byte, error) {
   115  	listener := NewFakeListener()
   116  	defer listener.Close()
   117  
   118  	// WARNING: We cannot depend on httptest.Server here. The reason is that we
   119  	// want to send a request as a slice of bytes. Requests sent to
   120  	// httptest.Server can only be sent using http.Client, which already uses
   121  	// the http.Request type. Using this type will make us obvlivious to any
   122  	// kind of request parsing problems in the Go standard library - and we want
   123  	// to test for these.
   124  	handler := &AssertHandler{callback: callback}
   125  	server := &http.Server{Handler: handler}
   126  	go server.Serve(listener)
   127  	defer server.Close()
   128  
   129  	if err := listener.SendRequest(req); err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	resp := make([]byte, 4096)
   134  	n, err := listener.ReadResponse(resp)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	if n == 4096 {
   139  		return nil, errors.New("response larger than or equal to 4096 bytes")
   140  	}
   141  	return resp[:n], server.Close() /* Forceful shutdown. We don't want to delay anything. */
   142  }