...

Source file src/cuelang.org/go/pkg/tool/http/http.go

Documentation: cuelang.org/go/pkg/tool/http

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

View as plain text