331 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package mailgun
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"mime/multipart"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| var validURL = regexp.MustCompile(`^/v[2-4].*`)
 | |
| 
 | |
| type httpRequest struct {
 | |
| 	URL               string
 | |
| 	Parameters        map[string][]string
 | |
| 	Headers           map[string]string
 | |
| 	BasicAuthUser     string
 | |
| 	BasicAuthPassword string
 | |
| 	Client            *http.Client
 | |
| }
 | |
| 
 | |
| type httpResponse struct {
 | |
| 	Code int
 | |
| 	Data []byte
 | |
| }
 | |
| 
 | |
| type payload interface {
 | |
| 	getPayloadBuffer() (*bytes.Buffer, error)
 | |
| 	getContentType() string
 | |
| 	getValues() []keyValuePair
 | |
| }
 | |
| 
 | |
| type keyValuePair struct {
 | |
| 	key   string
 | |
| 	value string
 | |
| }
 | |
| 
 | |
| type keyNameRC struct {
 | |
| 	key   string
 | |
| 	name  string
 | |
| 	value io.ReadCloser
 | |
| }
 | |
| 
 | |
| type keyNameBuff struct {
 | |
| 	key   string
 | |
| 	name  string
 | |
| 	value []byte
 | |
| }
 | |
| 
 | |
| type formDataPayload struct {
 | |
| 	contentType string
 | |
| 	Values      []keyValuePair
 | |
| 	Files       []keyValuePair
 | |
| 	ReadClosers []keyNameRC
 | |
| 	Buffers     []keyNameBuff
 | |
| }
 | |
| 
 | |
| type urlEncodedPayload struct {
 | |
| 	Values []keyValuePair
 | |
| }
 | |
| 
 | |
| func newHTTPRequest(url string) *httpRequest {
 | |
| 	return &httpRequest{URL: url, Client: http.DefaultClient}
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) addParameter(name, value string) {
 | |
| 	if r.Parameters == nil {
 | |
| 		r.Parameters = make(map[string][]string)
 | |
| 	}
 | |
| 	r.Parameters[name] = append(r.Parameters[name], value)
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) setClient(c *http.Client) {
 | |
| 	r.Client = c
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) setBasicAuth(user, password string) {
 | |
| 	r.BasicAuthUser = user
 | |
| 	r.BasicAuthPassword = password
 | |
| }
 | |
| 
 | |
| func newUrlEncodedPayload() *urlEncodedPayload {
 | |
| 	return &urlEncodedPayload{}
 | |
| }
 | |
| 
 | |
| func (f *urlEncodedPayload) addValue(key, value string) {
 | |
| 	f.Values = append(f.Values, keyValuePair{key: key, value: value})
 | |
| }
 | |
| 
 | |
| func (f *urlEncodedPayload) getPayloadBuffer() (*bytes.Buffer, error) {
 | |
| 	data := url.Values{}
 | |
| 	for _, keyVal := range f.Values {
 | |
| 		data.Add(keyVal.key, keyVal.value)
 | |
| 	}
 | |
| 	return bytes.NewBufferString(data.Encode()), nil
 | |
| }
 | |
| 
 | |
| func (f *urlEncodedPayload) getContentType() string {
 | |
| 	return "application/x-www-form-urlencoded"
 | |
| }
 | |
| 
 | |
| func (f *urlEncodedPayload) getValues() []keyValuePair {
 | |
| 	return f.Values
 | |
| }
 | |
| 
 | |
| func (r *httpResponse) parseFromJSON(v interface{}) error {
 | |
| 	return json.Unmarshal(r.Data, v)
 | |
| }
 | |
| 
 | |
| func newFormDataPayload() *formDataPayload {
 | |
| 	return &formDataPayload{}
 | |
| }
 | |
| 
 | |
| func (f *formDataPayload) getValues() []keyValuePair {
 | |
| 	return f.Values
 | |
| }
 | |
| 
 | |
| func (f *formDataPayload) addValue(key, value string) {
 | |
| 	f.Values = append(f.Values, keyValuePair{key: key, value: value})
 | |
| }
 | |
| 
 | |
| func (f *formDataPayload) addFile(key, file string) {
 | |
| 	f.Files = append(f.Files, keyValuePair{key: key, value: file})
 | |
| }
 | |
| 
 | |
| func (f *formDataPayload) addBuffer(key, file string, buff []byte) {
 | |
| 	f.Buffers = append(f.Buffers, keyNameBuff{key: key, name: file, value: buff})
 | |
| }
 | |
| 
 | |
| func (f *formDataPayload) addReadCloser(key, name string, rc io.ReadCloser) {
 | |
| 	f.ReadClosers = append(f.ReadClosers, keyNameRC{key: key, name: name, value: rc})
 | |
| }
 | |
| 
 | |
| func (f *formDataPayload) getPayloadBuffer() (*bytes.Buffer, error) {
 | |
| 	data := &bytes.Buffer{}
 | |
| 	writer := multipart.NewWriter(data)
 | |
| 	defer writer.Close()
 | |
| 
 | |
| 	for _, keyVal := range f.Values {
 | |
| 		if tmp, err := writer.CreateFormField(keyVal.key); err == nil {
 | |
| 			tmp.Write([]byte(keyVal.value))
 | |
| 		} else {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, file := range f.Files {
 | |
| 		if tmp, err := writer.CreateFormFile(file.key, path.Base(file.value)); err == nil {
 | |
| 			if fp, err := os.Open(file.value); err == nil {
 | |
| 				defer fp.Close()
 | |
| 				io.Copy(tmp, fp)
 | |
| 			} else {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 		} else {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, file := range f.ReadClosers {
 | |
| 		if tmp, err := writer.CreateFormFile(file.key, file.name); err == nil {
 | |
| 			defer file.value.Close()
 | |
| 			io.Copy(tmp, file.value)
 | |
| 		} else {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, buff := range f.Buffers {
 | |
| 		if tmp, err := writer.CreateFormFile(buff.key, buff.name); err == nil {
 | |
| 			r := bytes.NewReader(buff.value)
 | |
| 			io.Copy(tmp, r)
 | |
| 		} else {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	f.contentType = writer.FormDataContentType()
 | |
| 
 | |
| 	return data, nil
 | |
| }
 | |
| 
 | |
| func (f *formDataPayload) getContentType() string {
 | |
| 	if f.contentType == "" {
 | |
| 		f.getPayloadBuffer()
 | |
| 	}
 | |
| 	return f.contentType
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) addHeader(name, value string) {
 | |
| 	if r.Headers == nil {
 | |
| 		r.Headers = make(map[string]string)
 | |
| 	}
 | |
| 	r.Headers[name] = value
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) makeGetRequest(ctx context.Context) (*httpResponse, error) {
 | |
| 	return r.makeRequest(ctx, "GET", nil)
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) makePostRequest(ctx context.Context, payload payload) (*httpResponse, error) {
 | |
| 	return r.makeRequest(ctx, "POST", payload)
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) makePutRequest(ctx context.Context, payload payload) (*httpResponse, error) {
 | |
| 	return r.makeRequest(ctx, "PUT", payload)
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) makeDeleteRequest(ctx context.Context) (*httpResponse, error) {
 | |
| 	return r.makeRequest(ctx, "DELETE", nil)
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) NewRequest(ctx context.Context, method string, payload payload) (*http.Request, error) {
 | |
| 	url, err := r.generateUrlWithParameters()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	var body io.Reader
 | |
| 	if payload != nil {
 | |
| 		if body, err = payload.getPayloadBuffer(); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	} else {
 | |
| 		body = nil
 | |
| 	}
 | |
| 	req, err := http.NewRequest(method, url, body)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	req = req.WithContext(ctx)
 | |
| 
 | |
| 	if payload != nil && payload.getContentType() != "" {
 | |
| 		req.Header.Add("Content-Type", payload.getContentType())
 | |
| 	}
 | |
| 
 | |
| 	if r.BasicAuthUser != "" && r.BasicAuthPassword != "" {
 | |
| 		req.SetBasicAuth(r.BasicAuthUser, r.BasicAuthPassword)
 | |
| 	}
 | |
| 
 | |
| 	for header, value := range r.Headers {
 | |
| 		req.Header.Add(header, value)
 | |
| 	}
 | |
| 	return req, nil
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) makeRequest(ctx context.Context, method string, payload payload) (*httpResponse, error) {
 | |
| 	req, err := r.NewRequest(ctx, method, payload)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if Debug {
 | |
| 		fmt.Println(r.curlString(req, payload))
 | |
| 	}
 | |
| 
 | |
| 	response := httpResponse{}
 | |
| 
 | |
| 	resp, err := r.Client.Do(req)
 | |
| 	if resp != nil {
 | |
| 		response.Code = resp.StatusCode
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		if urlErr, ok := err.(*url.Error); ok {
 | |
| 			if urlErr.Err == io.EOF {
 | |
| 				return nil, errors.Wrap(err, "remote server prematurely closed connection")
 | |
| 			}
 | |
| 		}
 | |
| 		return nil, errors.Wrap(err, "while making http request")
 | |
| 	}
 | |
| 
 | |
| 	defer resp.Body.Close()
 | |
| 	responseBody, err := ioutil.ReadAll(resp.Body)
 | |
| 	if err != nil {
 | |
| 		return nil, errors.Wrap(err, "while reading response body")
 | |
| 	}
 | |
| 
 | |
| 	response.Data = responseBody
 | |
| 	return &response, nil
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) generateUrlWithParameters() (string, error) {
 | |
| 	url, err := url.Parse(r.URL)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	if !validURL.MatchString(url.Path) {
 | |
| 		return "", errors.New(`BaseAPI must end with a /v2, /v3 or /v4; setBaseAPI("https://host/v3")`)
 | |
| 	}
 | |
| 
 | |
| 	q := url.Query()
 | |
| 	if r.Parameters != nil && len(r.Parameters) > 0 {
 | |
| 		for name, values := range r.Parameters {
 | |
| 			for _, value := range values {
 | |
| 				q.Add(name, value)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	url.RawQuery = q.Encode()
 | |
| 
 | |
| 	return url.String(), nil
 | |
| }
 | |
| 
 | |
| func (r *httpRequest) curlString(req *http.Request, p payload) string {
 | |
| 
 | |
| 	parts := []string{"curl", "-i", "-X", req.Method, req.URL.String()}
 | |
| 	for key, value := range req.Header {
 | |
| 		parts = append(parts, fmt.Sprintf("-H \"%s: %s\"", key, value[0]))
 | |
| 	}
 | |
| 
 | |
| 	//parts = append(parts, fmt.Sprintf(" --user '%s:%s'", r.BasicAuthUser, r.BasicAuthPassword))
 | |
| 
 | |
| 	if p != nil {
 | |
| 		for _, param := range p.getValues() {
 | |
| 			parts = append(parts, fmt.Sprintf(" -F %s='%s'", param.key, param.value))
 | |
| 		}
 | |
| 	}
 | |
| 	return strings.Join(parts, " ")
 | |
| }
 |