Code review suggestions
parent
171f6a4971
commit
ac536767ce
@ -1,87 +1,82 @@
|
|||||||
package localapiclient
|
package localapiclient
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/ipn/localapi"
|
"tailscale.com/ipn/localapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LocalAPIResponseWriter substitutes for http.ResponseWriter in order to write byte streams directly
|
// Response represents the result of processing an http.Request.
|
||||||
// to a receiver function in the application.
|
type Response struct {
|
||||||
type LocalApiResponseWriter struct {
|
|
||||||
headers http.Header
|
headers http.Header
|
||||||
body bytes.Buffer
|
|
||||||
status int
|
status int
|
||||||
|
bodyWriter io.WriteCloser
|
||||||
|
bodyReader io.ReadCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLocalApiResponseWriter() *LocalApiResponseWriter {
|
func (r *Response) Header() http.Header {
|
||||||
return &LocalApiResponseWriter{headers: http.Header{}, status: http.StatusOK}
|
return r.headers
|
||||||
}
|
|
||||||
|
|
||||||
func (w *LocalApiResponseWriter) Header() http.Header {
|
|
||||||
return w.headers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes the data to the response body, which will be sent to Java. If WriteHeader is not called
|
// Write writes the data to the response body, which will be sent to Java. If WriteHeader is not called
|
||||||
// explicitly, the first call to Write will trigger an implicit WriteHeader(http.StatusOK).
|
// explicitly, the first call to Write will trigger an implicit WriteHeader(http.StatusOK).
|
||||||
func (w *LocalApiResponseWriter) Write(data []byte) (int, error) {
|
func (r *Response) Write(data []byte) (int, error) {
|
||||||
if w.status == 0 {
|
if r.status == 0 {
|
||||||
w.WriteHeader(http.StatusOK)
|
r.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
return w.body.Write(data)
|
return r.bodyWriter.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LocalApiResponseWriter) WriteHeader(statusCode int) {
|
func (r *Response) WriteHeader(statusCode int) {
|
||||||
w.status = statusCode
|
r.status = statusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LocalApiResponseWriter) Body() []byte {
|
func (r *Response) Body() io.ReadCloser {
|
||||||
return w.body.Bytes()
|
return r.bodyReader
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LocalApiResponseWriter) StatusCode() int {
|
func (r *Response) StatusCode() int {
|
||||||
return w.status
|
return r.status
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocalApiClient struct {
|
type LocalAPIClient struct {
|
||||||
h *localapi.Handler
|
h *localapi.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalApiClient(h *localapi.Handler) LocalApiClient {
|
func New(h *localapi.Handler) *LocalAPIClient {
|
||||||
return LocalApiClient{h: h}
|
return &LocalAPIClient{h: h}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrBadHttpStatus = errors.New("bad http status for localapi response")
|
// Call calls the given endpoint on the local API using the given HTTP method
|
||||||
|
// optionally sending the given body. It returns a Response representing the
|
||||||
|
// result of the call and an error if the call could not be completed or the
|
||||||
|
// local API returned a status code in the 400 series or greater.
|
||||||
|
// Note - Response includes a response body available from the Body method, it
|
||||||
|
// is the caller's responsibility to close this.
|
||||||
|
func (cl *LocalAPIClient) Call(ctx context.Context, method, endpoint string, body io.Reader) (*Response, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
func CallLocalApi(h *localapi.Handler, method string, endpoint string) (*LocalApiResponseWriter, error) {
|
req, err := http.NewRequestWithContext(ctx, method, "/localapi/v0/"+endpoint, body)
|
||||||
done := make(chan *LocalApiResponseWriter, 1)
|
|
||||||
var responseError error
|
|
||||||
go func() {
|
|
||||||
req, err := http.NewRequest(method, "/localapi/v0/"+endpoint, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error creating new request for %s: %v", endpoint, err)
|
return nil, fmt.Errorf("error creating new request for %s: %w", endpoint, err)
|
||||||
responseError = err
|
|
||||||
close(done)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
w := newLocalApiResponseWriter()
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
h.ServeHTTP(w, req)
|
defer pipeWriter.Close()
|
||||||
if w.StatusCode() > 300 {
|
|
||||||
log.Printf("%s bad http status: %v", endpoint, w.StatusCode())
|
|
||||||
responseError = ErrBadHttpStatus
|
|
||||||
}
|
|
||||||
done <- w
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
resp := &Response{
|
||||||
case w := <-done:
|
headers: http.Header{},
|
||||||
return w, responseError
|
status: http.StatusOK,
|
||||||
case <-time.After(2 * time.Second):
|
bodyReader: pipeReader,
|
||||||
return nil, fmt.Errorf("request to %s timed out", endpoint)
|
bodyWriter: pipeWriter,
|
||||||
|
}
|
||||||
|
cl.h.ServeHTTP(resp, req)
|
||||||
|
if resp.StatusCode() >= 400 {
|
||||||
|
return resp, fmt.Errorf("request failed with status code %d", resp.StatusCode())
|
||||||
}
|
}
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue