request.go raw

   1  /*
   2   * Copyright 2017 Baidu, Inc.
   3   *
   4   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
   5   * except in compliance with the License. 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 distributed under the
  10   * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  11   * either express or implied. See the License for the specific language governing permissions
  12   * and limitations under the License.
  13   */
  14  
  15  // request.go - defines the BCE servies request
  16  
  17  package bce
  18  
  19  import (
  20  	"bytes"
  21  	"fmt"
  22  	"hash"
  23  	"io"
  24  	"io/ioutil"
  25  	"os"
  26  
  27  	"github.com/baidubce/bce-sdk-go/http"
  28  	"github.com/baidubce/bce-sdk-go/util"
  29  )
  30  
  31  // Body defines the data structure used in BCE request.
  32  // Every BCE request that sets the body field must set its content-length and content-md5 headers
  33  // to ensure the correctness of the body content forcely, and users can also set the content-sha256
  34  // header to strengthen the correctness with the "SetHeader" method.
  35  type Body struct {
  36  	stream     io.ReadCloser
  37  	writer     io.Writer
  38  	size       int64
  39  	contentMD5 string
  40  }
  41  
  42  func (b *Body) Stream() io.ReadCloser { return b.stream }
  43  
  44  func (b *Body) SetStream(stream io.ReadCloser) { b.stream = stream }
  45  
  46  func (b *Body) Writer() io.Writer { return b.writer }
  47  
  48  func (b *Body) SetWriter(w io.Writer) { b.writer = w }
  49  
  50  func (b *Body) Size() int64 { return b.size }
  51  
  52  func (b *Body) SetSize(size int64) { b.size = size }
  53  
  54  func (b *Body) ContentMD5() string { return b.contentMD5 }
  55  
  56  func (b *Body) SetContentMD5(md5Str string) { b.contentMD5 = md5Str }
  57  
  58  func (b *Body) Read(p []byte) (int, error) {
  59  	n, err := b.stream.Read(p)
  60  	if n > 0 && b.writer != nil {
  61  		if n, err := b.writer.Write(p[:n]); err != nil {
  62  			return n, err
  63  		}
  64  	}
  65  	return n, err
  66  }
  67  
  68  func (b *Body) Close() error {
  69  	if rclose, ok := b.stream.(io.ReadCloser); ok {
  70  		rclose.Close()
  71  	}
  72  	if b.writer != nil {
  73  		if wclose, ok := b.writer.(io.WriteCloser); ok {
  74  			wclose.Close()
  75  		}
  76  	}
  77  	return nil
  78  }
  79  
  80  func (b *Body) Crc32() uint32 {
  81  	if b.writer != nil {
  82  		if hc32, ok := b.writer.(hash.Hash32); ok {
  83  			return hc32.Sum32()
  84  		}
  85  	}
  86  	return 0
  87  }
  88  
  89  // NewBodyFromBytes - build a Body object from the byte stream to be used in the http request, it
  90  // calculates the content-md5 of the byte stream and store the size as well as the stream.
  91  //
  92  // PARAMS:
  93  //   - stream: byte stream
  94  //
  95  // RETURNS:
  96  //   - *Body: the return Body object
  97  //   - error: error if any specific error occurs
  98  func NewBodyFromBytes(stream []byte) (*Body, error) {
  99  	buf := bytes.NewBuffer(stream)
 100  	size := int64(buf.Len())
 101  	contentMD5, err := util.CalculateContentMD5(buf, size)
 102  	if err != nil {
 103  		return nil, err
 104  	}
 105  	buf = bytes.NewBuffer(stream)
 106  	return &Body{stream: ioutil.NopCloser(buf), size: size, contentMD5: contentMD5}, nil
 107  }
 108  
 109  // NewBodyFromString - build a Body object from the string to be used in the http request, it
 110  // calculates the content-md5 of the byte stream and store the size as well as the stream.
 111  //
 112  // PARAMS:
 113  //   - str: the input string
 114  //
 115  // RETURNS:
 116  //   - *Body: the return Body object
 117  //   - error: error if any specific error occurs
 118  func NewBodyFromString(str string) (*Body, error) {
 119  	buf := bytes.NewBufferString(str)
 120  	size := int64(len(str))
 121  	contentMD5, err := util.CalculateContentMD5(buf, size)
 122  	if err != nil {
 123  		return nil, err
 124  	}
 125  	buf = bytes.NewBufferString(str)
 126  	return &Body{stream: ioutil.NopCloser(buf), size: size, contentMD5: contentMD5}, nil
 127  }
 128  
 129  // NewBodyFromFile - build a Body object from the given file name to be used in the http request,
 130  // it calculates the content-md5 of the byte stream and store the size as well as the stream.
 131  //
 132  // PARAMS:
 133  //   - fname: the given file name
 134  //
 135  // RETURNS:
 136  //   - *Body: the return Body object
 137  //   - error: error if any specific error occurs
 138  func NewBodyFromFile(fname string) (*Body, error) {
 139  	file, err := os.Open(fname)
 140  	if err != nil {
 141  		return nil, err
 142  	}
 143  	fileInfo, infoErr := file.Stat()
 144  	if infoErr != nil {
 145  		return nil, infoErr
 146  	}
 147  	contentMD5, md5Err := util.CalculateContentMD5(file, fileInfo.Size())
 148  	if md5Err != nil {
 149  		return nil, md5Err
 150  	}
 151  	if _, err = file.Seek(0, 0); err != nil {
 152  		return nil, err
 153  	}
 154  	return &Body{stream: file, size: fileInfo.Size(), contentMD5: contentMD5}, nil
 155  }
 156  
 157  // NewBodyFromSectionFile - build a Body object from the given file pointer with offset and size.
 158  // It calculates the content-md5 of the given content and store the size as well as the stream.
 159  //
 160  // PARAMS:
 161  //   - file: the input file pointer
 162  //   - off: offset of current section body
 163  //   - size: current section body size
 164  //
 165  // RETURNS:
 166  //   - *Body: the return Body object
 167  //   - error: error if any specific error occurs
 168  func NewBodyFromSectionFile(file *os.File, off, size int64) (*Body, error) {
 169  	if _, err := file.Seek(off, 0); err != nil {
 170  		return nil, err
 171  	}
 172  	contentMD5, md5Err := util.CalculateContentMD5(file, size)
 173  	if md5Err != nil {
 174  		return nil, md5Err
 175  	}
 176  	if _, err := file.Seek(0, 0); err != nil {
 177  		return nil, err
 178  	}
 179  	section := io.NewSectionReader(file, off, size)
 180  	return &Body{stream: ioutil.NopCloser(section), size: size, contentMD5: contentMD5}, nil
 181  }
 182  
 183  // NewBodyFromSizedReader - build a Body object from the given reader with size.
 184  // It calculates the content-md5 of the given content and store the size as well as the stream.
 185  //
 186  // PARAMS:
 187  //   - r: the input reader
 188  //   - size: the size to be read, -1 is read all
 189  //
 190  // RETURNS:
 191  //   - *Body: the return Body object
 192  //   - error: error if any specific error occurs
 193  func NewBodyFromSizedReader(r io.Reader, size int64) (*Body, error) {
 194  	var buffer bytes.Buffer
 195  	var rlen int64
 196  	var err error
 197  	if size >= 0 {
 198  		rlen, err = io.CopyN(&buffer, r, size)
 199  	} else {
 200  		rlen, err = io.Copy(&buffer, r)
 201  	}
 202  	if err != nil {
 203  		return nil, err
 204  	}
 205  	if rlen != int64(buffer.Len()) { // must be equal
 206  		return nil, NewBceClientError("unexpected reader")
 207  	}
 208  	if size >= 0 {
 209  		if rlen < size {
 210  			return nil, NewBceClientError("size is great than reader actual size")
 211  		}
 212  	}
 213  	contentMD5, err := util.CalculateContentMD5(bytes.NewBuffer(buffer.Bytes()), rlen)
 214  	if err != nil {
 215  		return nil, err
 216  	}
 217  	body := &Body{
 218  		stream:     ioutil.NopCloser(&buffer),
 219  		size:       rlen,
 220  		contentMD5: contentMD5,
 221  	}
 222  	return body, nil
 223  }
 224  
 225  // BceRequest defines the request structure for accessing BCE services
 226  type BceRequest struct {
 227  	http.Request
 228  	requestId   string
 229  	clientError *BceClientError
 230  }
 231  
 232  func (b *BceRequest) RequestId() string { return b.requestId }
 233  
 234  func (b *BceRequest) SetRequestId(val string) { b.requestId = val }
 235  
 236  func (b *BceRequest) ClientError() *BceClientError { return b.clientError }
 237  
 238  func (b *BceRequest) SetClientError(err *BceClientError) { b.clientError = err }
 239  
 240  func (b *BceRequest) SetBody(body *Body) { // override SetBody derived from http.Request
 241  	b.Request.SetBody(body)
 242  	b.SetLength(body.Size()) // set field of "net/http.Request.ContentLength"
 243  	if body.ContentMD5() != "" {
 244  		b.SetHeader(http.CONTENT_MD5, body.ContentMD5())
 245  	}
 246  	if body.Size() > 0 {
 247  		b.SetHeader(http.CONTENT_LENGTH, fmt.Sprintf("%d", body.Size()))
 248  	}
 249  }
 250  
 251  func (b *BceRequest) BuildHttpRequest() {
 252  	// Only need to build the specific `requestId` field for BCE, other fields are same as the
 253  	// `http.Request` as well as its methods.
 254  	if len(b.requestId) == 0 {
 255  		// Construct the request ID with UUID
 256  		b.requestId = util.NewRequestId()
 257  	}
 258  	b.SetHeader(http.BCE_REQUEST_ID, b.requestId)
 259  }
 260  
 261  func (b *BceRequest) String() string {
 262  	requestIdStr := "requestId=" + b.requestId
 263  	if b.clientError != nil {
 264  		return requestIdStr + ", client error: " + b.ClientError().Error()
 265  	}
 266  	return requestIdStr + "\n" + b.Request.String()
 267  }
 268