github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/pkg/tool/http/http.go (about)

     1  // Copyright 2019 CUE Authors
     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  //     http://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 http
    16  
    17  //go:generate go run gen.go
    18  //go:generate gofmt -s -w .
    19  
    20  import (
    21  	"bytes"
    22  	"crypto/tls"
    23  	"crypto/x509"
    24  	"encoding/pem"
    25  	"io"
    26  	"io/ioutil"
    27  	"net/http"
    28  
    29  	"github.com/joomcode/cue/cue"
    30  	"github.com/joomcode/cue/cue/errors"
    31  	"github.com/joomcode/cue/internal/task"
    32  )
    33  
    34  func init() {
    35  	task.Register("tool/http.Do", newHTTPCmd)
    36  
    37  	// For backwards compatibility.
    38  	task.Register("http", newHTTPCmd)
    39  }
    40  
    41  type httpCmd struct{}
    42  
    43  func newHTTPCmd(v cue.Value) (task.Runner, error) {
    44  	return &httpCmd{}, nil
    45  }
    46  
    47  func (c *httpCmd) Run(ctx *task.Context) (res interface{}, err error) {
    48  	var header, trailer http.Header
    49  	var (
    50  		method = ctx.String("method")
    51  		u      = ctx.String("url")
    52  	)
    53  	var r io.Reader
    54  	if obj := ctx.Obj.Lookup("request"); obj.Exists() {
    55  		if v := obj.Lookup("body"); v.Exists() {
    56  			r, err = v.Reader()
    57  			if err != nil {
    58  				return nil, err
    59  			}
    60  		} else {
    61  			r = bytes.NewReader([]byte(""))
    62  		}
    63  		if header, err = parseHeaders(obj, "header"); err != nil {
    64  			return nil, err
    65  		}
    66  		if trailer, err = parseHeaders(obj, "trailer"); err != nil {
    67  			return nil, err
    68  		}
    69  	}
    70  
    71  	var caCert []byte
    72  	caCertValue := ctx.Obj.LookupPath(cue.ParsePath("tls.caCert"))
    73  	if caCertValue.Exists() {
    74  		caCert, err = caCertValue.Bytes()
    75  		if err != nil {
    76  			return nil, errors.Wrapf(err, caCertValue.Pos(), "invalid bytes value")
    77  		}
    78  	}
    79  
    80  	tlsVerify := true
    81  	tlsVerifyValue := ctx.Obj.LookupPath(cue.ParsePath("tls.verify"))
    82  	if tlsVerifyValue.Exists() {
    83  		tlsVerify, err = tlsVerifyValue.Bool()
    84  		if err != nil {
    85  			return nil, errors.Wrapf(err, tlsVerifyValue.Pos(), "invalid bool value")
    86  		}
    87  	}
    88  
    89  	if ctx.Err != nil {
    90  		return nil, ctx.Err
    91  	}
    92  
    93  	transport := http.DefaultTransport.(*http.Transport).Clone()
    94  	transport.TLSClientConfig = &tls.Config{}
    95  
    96  	if !tlsVerify {
    97  		transport.TLSClientConfig.InsecureSkipVerify = true
    98  	}
    99  	if tlsVerify && len(caCert) > 0 {
   100  		pool := x509.NewCertPool()
   101  		for {
   102  			block, rest := pem.Decode(caCert)
   103  			if block == nil {
   104  				break
   105  			}
   106  			if block.Type == "PUBLIC KEY" {
   107  				c, err := x509.ParseCertificate(block.Bytes)
   108  				if err != nil {
   109  					return nil, errors.Wrapf(err, ctx.Obj.Pos(), "failed to parse caCert")
   110  				}
   111  				pool.AddCert(c)
   112  			}
   113  			caCert = rest
   114  		}
   115  		transport.TLSClientConfig.RootCAs = pool
   116  	}
   117  
   118  	client := &http.Client{
   119  		Transport: transport,
   120  		// TODO: timeout
   121  	}
   122  
   123  	req, err := http.NewRequest(method, u, r)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	req.Header = header
   128  	req.Trailer = trailer
   129  
   130  	// TODO: retry logic
   131  	resp, err := client.Do(req)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	defer resp.Body.Close()
   136  	b, err := ioutil.ReadAll(resp.Body)
   137  	// parse response body and headers
   138  	return map[string]interface{}{
   139  		"response": map[string]interface{}{
   140  			"status":     resp.Status,
   141  			"statusCode": resp.StatusCode,
   142  			"body":       string(b),
   143  			"header":     resp.Header,
   144  			"trailer":    resp.Trailer,
   145  		},
   146  	}, err
   147  }
   148  
   149  func parseHeaders(obj cue.Value, label string) (http.Header, error) {
   150  	m := obj.Lookup(label)
   151  	if !m.Exists() {
   152  		return nil, nil
   153  	}
   154  	iter, err := m.Fields()
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	h := http.Header{}
   159  	for iter.Next() {
   160  		str, err := iter.Value().String()
   161  		if err != nil {
   162  			return nil, err
   163  		}
   164  		h.Add(iter.Label(), str)
   165  	}
   166  	return h, nil
   167  }