github.com/GeniusesGroup/libgo@v0.0.0-20220929090155-5ff932cb408e/http/response.go (about)

     1  /* For license and copyright information please see the LEGAL file in the code repository */
     2  
     3  package http
     4  
     5  import (
     6  	"io"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/GeniusesGroup/libgo/codec"
    11  	"github.com/GeniusesGroup/libgo/convert"
    12  	"github.com/GeniusesGroup/libgo/protocol"
    13  )
    14  
    15  // Response is represent response protocol structure!
    16  // https://tools.ietf.org/html/rfc2616#section-6
    17  type Response struct {
    18  	version      string
    19  	statusCode   string
    20  	reasonPhrase string
    21  
    22  	H header // Exported field to let consumers use other methods that protocol.HTTPHeader
    23  	body
    24  }
    25  
    26  func (r *Response) Init() { 
    27  	r.H.Init()
    28  	r.body.Init()
    29  }
    30  func (r *Response) Reinit() {
    31  	r.version = ""
    32  	r.statusCode = ""
    33  	r.reasonPhrase = ""
    34  	r.H.Reinit()
    35  	r.body.Reinit()
    36  }
    37  func (r *Response) Deinit() {
    38  	r.H.Deinit()
    39  	r.body.Deinit()
    40  }
    41  
    42  func (r *Response) Version() string               { return r.version }
    43  func (r *Response) StatusCode() string            { return r.statusCode }
    44  func (r *Response) ReasonPhrase() string          { return r.reasonPhrase }
    45  func (r *Response) SetVersion(version string)     { r.version = version }
    46  func (r *Response) SetStatus(code, phrase string) { r.statusCode = code; r.reasonPhrase = phrase }
    47  func (r *Response) Header() protocol.HTTPHeader   { return &r.H }
    48  
    49  // GetStatusCode get status code as uit16
    50  func (r *Response) GetStatusCode() (code uint16, err protocol.Error) {
    51  	// TODO::: don't use strconv for such simple task
    52  	var c, goErr = strconv.ParseUint(r.statusCode, 10, 16)
    53  	if goErr != nil {
    54  		return 0, &ErrParseStatusCode
    55  	}
    56  	return uint16(c), nil
    57  }
    58  
    59  // GetError return related protocol.Error in header of the Response
    60  func (r *Response) GetError() (err protocol.Error) {
    61  	var errIDString = r.H.Get(HeaderKeyErrorID)
    62  	var errID, _ = strconv.ParseUint(errIDString, 10, 64)
    63  	if errID == 0 {
    64  		return
    65  	}
    66  	err = protocol.App.GetErrorByID(protocol.ID(errID))
    67  	return
    68  }
    69  
    70  // SetError set given protocol.Error to header of the response
    71  func (r *Response) SetError(err protocol.Error) {
    72  	r.H.Set(HeaderKeyErrorID, err.IDasString())
    73  }
    74  
    75  // Redirect set given status and target location to the response
    76  // httpRes.Redirect(http.StatusMovedPermanentlyCode, http.StatusMovedPermanentlyPhrase, "http://www.google.com/")
    77  func (r *Response) Redirect(code, phrase string, target string) {
    78  	r.SetStatus(code, phrase)
    79  	r.H.Set(HeaderKeyLocation, target)
    80  }
    81  
    82  //libgo:impl protocol.Codec
    83  func (r *Response) MediaType() protocol.MediaType       { return &MediaTypeResponse }
    84  func (r *Response) CompressType() protocol.CompressType { return nil }
    85  func (r *Response) Len() (ln int) {
    86  	ln = r.LenWithoutBody()
    87  	ln += r.body.Len()
    88  	return
    89  }
    90  func (r *Response) Decode(source protocol.Codec) (n int, err protocol.Error) {
    91  	if source.Len() > MaxHTTPHeaderSize {
    92  		// err =
    93  		return
    94  	}
    95  
    96  	// Make a buffer to hold incoming data.
    97  	// TODO::: change to get from buffer pool??
    98  	var buf = make([]byte, 0, MaxHTTPHeaderSize)
    99  	// Read the incoming connection into the buffer.
   100  	buf, err = source.MarshalTo(buf)
   101  	if err != nil {
   102  		// err = connection.ErrNoConnection
   103  		return
   104  	}
   105  
   106  	buf, err = r.UnmarshalFrom(buf)
   107  	if err != nil {
   108  		return
   109  	}
   110  	err = r.body.checkAndSetCodecAsIncomeBody(buf, source, &r.H)
   111  	return
   112  }
   113  func (r *Response) Encode(destination protocol.Codec) (n int, err protocol.Error) {
   114  	var lenWithoutBody = r.LenWithoutBody()
   115  	var bodyLen = r.body.Len()
   116  	var wholeLen = lenWithoutBody + bodyLen
   117  	// Check if whole request has fewer length than MaxHTTPHeaderSize and Decide to send header and body separately
   118  	if wholeLen > MaxHTTPHeaderSize {
   119  		var withoutBody = make([]byte, 0, lenWithoutBody)
   120  		withoutBody = r.MarshalToWithoutBody(withoutBody)
   121  
   122  		n, err = destination.Unmarshal(withoutBody)
   123  		if err == nil && r.body.Codec != nil {
   124  			var bodyWrote int
   125  			bodyWrote, err = destination.Encode(&r.body)
   126  			n += bodyWrote
   127  		}
   128  	} else {
   129  		var httpPacket = make([]byte, 0, wholeLen)
   130  		httpPacket, err = r.MarshalTo(httpPacket)
   131  		n, err = destination.Unmarshal(httpPacket)
   132  	}
   133  	return
   134  }
   135  
   136  // Marshal encodes whole r *Response data and return httpPacket!
   137  func (r *Response) Marshal() (httpPacket []byte, err protocol.Error) {
   138  	httpPacket = make([]byte, 0, r.Len())
   139  	httpPacket, err = r.MarshalTo(httpPacket)
   140  	return
   141  }
   142  
   143  // MarshalTo encodes whole r *Response data to given httpPacket and return it by new len!
   144  func (r *Response) MarshalTo(httpPacket []byte) (added []byte, err protocol.Error) {
   145  	httpPacket = append(httpPacket, r.version...)
   146  	httpPacket = append(httpPacket, SP)
   147  	httpPacket = append(httpPacket, r.statusCode...)
   148  	httpPacket = append(httpPacket, SP)
   149  	httpPacket = append(httpPacket, r.reasonPhrase...)
   150  	httpPacket = append(httpPacket, CRLF...)
   151  
   152  	httpPacket = r.H.MarshalTo(httpPacket)
   153  	httpPacket = append(httpPacket, CRLF...)
   154  
   155  	httpPacket, err = r.body.MarshalTo(httpPacket)
   156  	added = httpPacket
   157  	return
   158  }
   159  
   160  // Unmarshal parses and decodes data of given httpPacket to r *Response.
   161  // In some bad packet may occur panic, handle panic by recover otherwise app will crash and exit!
   162  func (r *Response) Unmarshal(httpPacket []byte) (n int, err protocol.Error) {
   163  	var maybeBody []byte
   164  	maybeBody, err = r.UnmarshalFrom(httpPacket)
   165  	if err != nil {
   166  		return
   167  	}
   168  	err = r.body.checkAndSetIncomeBody(maybeBody, &r.H)
   169  	n = len(httpPacket)
   170  	return
   171  }
   172  
   173  // UnmarshalFrom parses and decodes data of given httpPacket to r *Response.
   174  // In some bad packet may occur panic, handle panic by recover otherwise app will crash and exit!
   175  func (r *Response) UnmarshalFrom(httpPacket []byte) (maybeBody []byte, err protocol.Error) {
   176  	// By use unsafe pointer here all strings assign in Response will just point to httpPacket slice
   177  	// and no need to alloc lot of new memory locations and copy response line and headers keys & values!
   178  	var s = convert.UnsafeByteSliceToString(httpPacket)
   179  
   180  	// si hold s index and i hold s index in new sliced state.
   181  	var si, i int
   182  
   183  	// First line: HTTP/1.0 200 OK
   184  	i = strings.IndexByte(s[:versionMaxLength], SP)
   185  	if i == -1 {
   186  		maybeBody = httpPacket[si:]
   187  		err = &ErrParseVersion
   188  		return
   189  	}
   190  	r.version = s[:i]
   191  	i++ // +1 due to have ' '
   192  	si = i
   193  	s = s[i:]
   194  
   195  	i = strings.IndexByte(s[:statusCodeMaxLength], SP)
   196  	if i == -1 {
   197  		maybeBody = httpPacket[si:]
   198  		err = &ErrParseStatusCode
   199  		return
   200  	}
   201  	r.statusCode = s[:i]
   202  	i++ // +1 due to have ' '
   203  	si += i
   204  	s = s[i:]
   205  
   206  	i = strings.IndexByte(s, '\r')
   207  	if i == -1 {
   208  		maybeBody = httpPacket[si:]
   209  		err = &ErrParseReasonPhrase
   210  		return
   211  	}
   212  	r.reasonPhrase = s[:i]
   213  	i += 2 // +2 due to have "\r\n"
   214  	si += i
   215  	s = s[i:]
   216  
   217  	i, err = r.H.unmarshal(s)
   218  	if err != nil {
   219  		maybeBody = httpPacket[i:]
   220  		return
   221  	}
   222  	si += i
   223  	// By https://tools.ietf.org/html/rfc2616#section-4 very simple http packet must end with CRLF even packet without header or body!
   224  	// So it can be occur panic if very simple request end without any CRLF
   225  	si += 2 // +2 due to have "\r\n" after header end
   226  
   227  	return httpPacket[si:], nil
   228  }
   229  
   230  /*
   231  ********** protocol.Buffer interface **********
   232   */
   233  
   234  // ReadFrom decodes r *Response data by read from given io.Reader!
   235  // Declare to respect io.ReaderFrom interface!
   236  func (r *Response) ReadFrom(reader io.Reader) (n int64, goErr error) {
   237  	// Make a buffer to hold incoming data.
   238  	var buf = make([]byte, MaxHTTPHeaderSize)
   239  	var headerReadLength int
   240  	var err protocol.Error
   241  
   242  	// Read the incoming connection into the buffer.
   243  	headerReadLength, goErr = reader.Read(buf)
   244  	if goErr != nil || headerReadLength == 0 {
   245  		return
   246  	}
   247  
   248  	buf = buf[:headerReadLength]
   249  	buf, err = r.UnmarshalFrom(buf)
   250  	if err != nil {
   251  		return int64(headerReadLength), err
   252  	}
   253  	err = r.body.checkAndSetReaderAsIncomeBody(buf, codec.ReaderAdaptor{reader}, &r.H)
   254  	n = int64(headerReadLength)
   255  	return
   256  }
   257  
   258  // WriteTo encodes r *Response data and write it to given io.Writer!
   259  // Declare to respect io.WriterTo interface!
   260  func (r *Response) WriteTo(writer io.Writer) (n int64, err error) {
   261  	var lenWithoutBody = r.LenWithoutBody()
   262  	var bodyLen = r.body.Len()
   263  	var wholeLen = lenWithoutBody + bodyLen
   264  	// Check if whole request has fewer length than MaxHTTPHeaderSize and Decide to send header and body separately
   265  	if wholeLen > MaxHTTPHeaderSize {
   266  		var httpPacket = make([]byte, 0, lenWithoutBody)
   267  		httpPacket = r.MarshalToWithoutBody(httpPacket)
   268  
   269  		var headerWriteLength int
   270  		headerWriteLength, err = writer.Write(httpPacket)
   271  		if err == nil && r.body.Codec != nil {
   272  			n, err = r.body.WriteTo(writer)
   273  		}
   274  		n += int64(headerWriteLength)
   275  	} else {
   276  		var httpPacket = make([]byte, 0, wholeLen)
   277  		httpPacket, _ = r.MarshalTo(httpPacket)
   278  		var packetWriteLength int
   279  		packetWriteLength, err = writer.Write(httpPacket)
   280  		n = int64(packetWriteLength)
   281  	}
   282  	return
   283  }
   284  
   285  /*
   286  ********** local methods **********
   287   */
   288  
   289  // MarshalWithoutBody encodes r *Response data and return httpPacket without body part!
   290  func (r *Response) MarshalWithoutBody() (httpPacket []byte) {
   291  	httpPacket = make([]byte, 0, r.LenWithoutBody())
   292  	httpPacket = r.MarshalToWithoutBody(httpPacket)
   293  	return
   294  }
   295  
   296  // MarshalToWithoutBody encodes r *Response data and return httpPacket without body part!
   297  func (r *Response) MarshalToWithoutBody(httpPacket []byte) []byte {
   298  	httpPacket = append(httpPacket, r.version...)
   299  	httpPacket = append(httpPacket, SP)
   300  	httpPacket = append(httpPacket, r.statusCode...)
   301  	httpPacket = append(httpPacket, SP)
   302  	httpPacket = append(httpPacket, r.reasonPhrase...)
   303  	httpPacket = append(httpPacket, CRLF...)
   304  
   305  	httpPacket = r.H.MarshalTo(httpPacket)
   306  	httpPacket = append(httpPacket, CRLF...)
   307  	return httpPacket
   308  }
   309  
   310  // LenWithoutBody return length of response without body length!
   311  func (r *Response) LenWithoutBody() (ln int) {
   312  	ln = 6 // 6==1+1+2+2==len(SP)+len(SP)+len(CRLF)+len(CRLF)
   313  	ln += len(r.version)
   314  	ln += len(r.statusCode)
   315  	ln += len(r.reasonPhrase)
   316  	ln += r.H.Len()
   317  	return
   318  }