github.com/geniusesgroup/libgo@v0.0.0-20220713101832-828057a9d3d4/http/response.go (about)

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