198 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package gomemcached
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/binary"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
)
 | 
						|
 | 
						|
// The maximum reasonable body length to expect.
 | 
						|
// Anything larger than this will result in an error.
 | 
						|
// The current limit, 20MB, is the size limit supported by ep-engine.
 | 
						|
var MaxBodyLen = int(20 * 1024 * 1024)
 | 
						|
 | 
						|
// MCRequest is memcached Request
 | 
						|
type MCRequest struct {
 | 
						|
	// The command being issued
 | 
						|
	Opcode CommandCode
 | 
						|
	// The CAS (if applicable, or 0)
 | 
						|
	Cas uint64
 | 
						|
	// An opaque value to be returned with this request
 | 
						|
	Opaque uint32
 | 
						|
	// The vbucket to which this command belongs
 | 
						|
	VBucket uint16
 | 
						|
	// Command extras, key, and body
 | 
						|
	Extras, Key, Body, ExtMeta []byte
 | 
						|
	// Datatype identifier
 | 
						|
	DataType uint8
 | 
						|
}
 | 
						|
 | 
						|
// Size gives the number of bytes this request requires.
 | 
						|
func (req *MCRequest) Size() int {
 | 
						|
	return HDR_LEN + len(req.Extras) + len(req.Key) + len(req.Body) + len(req.ExtMeta)
 | 
						|
}
 | 
						|
 | 
						|
// A debugging string representation of this request
 | 
						|
func (req MCRequest) String() string {
 | 
						|
	return fmt.Sprintf("{MCRequest opcode=%s, bodylen=%d, key='%s'}",
 | 
						|
		req.Opcode, len(req.Body), req.Key)
 | 
						|
}
 | 
						|
 | 
						|
func (req *MCRequest) fillHeaderBytes(data []byte) int {
 | 
						|
 | 
						|
	pos := 0
 | 
						|
	data[pos] = REQ_MAGIC
 | 
						|
	pos++
 | 
						|
	data[pos] = byte(req.Opcode)
 | 
						|
	pos++
 | 
						|
	binary.BigEndian.PutUint16(data[pos:pos+2],
 | 
						|
		uint16(len(req.Key)))
 | 
						|
	pos += 2
 | 
						|
 | 
						|
	// 4
 | 
						|
	data[pos] = byte(len(req.Extras))
 | 
						|
	pos++
 | 
						|
	// Data type
 | 
						|
	if req.DataType != 0 {
 | 
						|
		data[pos] = byte(req.DataType)
 | 
						|
	}
 | 
						|
	pos++
 | 
						|
	binary.BigEndian.PutUint16(data[pos:pos+2], req.VBucket)
 | 
						|
	pos += 2
 | 
						|
 | 
						|
	// 8
 | 
						|
	binary.BigEndian.PutUint32(data[pos:pos+4],
 | 
						|
		uint32(len(req.Body)+len(req.Key)+len(req.Extras)+len(req.ExtMeta)))
 | 
						|
	pos += 4
 | 
						|
 | 
						|
	// 12
 | 
						|
	binary.BigEndian.PutUint32(data[pos:pos+4], req.Opaque)
 | 
						|
	pos += 4
 | 
						|
 | 
						|
	// 16
 | 
						|
	if req.Cas != 0 {
 | 
						|
		binary.BigEndian.PutUint64(data[pos:pos+8], req.Cas)
 | 
						|
	}
 | 
						|
	pos += 8
 | 
						|
 | 
						|
	if len(req.Extras) > 0 {
 | 
						|
		copy(data[pos:pos+len(req.Extras)], req.Extras)
 | 
						|
		pos += len(req.Extras)
 | 
						|
	}
 | 
						|
 | 
						|
	if len(req.Key) > 0 {
 | 
						|
		copy(data[pos:pos+len(req.Key)], req.Key)
 | 
						|
		pos += len(req.Key)
 | 
						|
	}
 | 
						|
 | 
						|
	return pos
 | 
						|
}
 | 
						|
 | 
						|
// HeaderBytes will return the wire representation of the request header
 | 
						|
// (with the extras and key).
 | 
						|
func (req *MCRequest) HeaderBytes() []byte {
 | 
						|
	data := make([]byte, HDR_LEN+len(req.Extras)+len(req.Key))
 | 
						|
 | 
						|
	req.fillHeaderBytes(data)
 | 
						|
 | 
						|
	return data
 | 
						|
}
 | 
						|
 | 
						|
// Bytes will return the wire representation of this request.
 | 
						|
func (req *MCRequest) Bytes() []byte {
 | 
						|
	data := make([]byte, req.Size())
 | 
						|
 | 
						|
	pos := req.fillHeaderBytes(data)
 | 
						|
 | 
						|
	if len(req.Body) > 0 {
 | 
						|
		copy(data[pos:pos+len(req.Body)], req.Body)
 | 
						|
	}
 | 
						|
 | 
						|
	if len(req.ExtMeta) > 0 {
 | 
						|
		copy(data[pos+len(req.Body):pos+len(req.Body)+len(req.ExtMeta)], req.ExtMeta)
 | 
						|
	}
 | 
						|
 | 
						|
	return data
 | 
						|
}
 | 
						|
 | 
						|
// Transmit will send this request message across a writer.
 | 
						|
func (req *MCRequest) Transmit(w io.Writer) (n int, err error) {
 | 
						|
	if len(req.Body) < 128 {
 | 
						|
		n, err = w.Write(req.Bytes())
 | 
						|
	} else {
 | 
						|
		n, err = w.Write(req.HeaderBytes())
 | 
						|
		if err == nil {
 | 
						|
			m := 0
 | 
						|
			m, err = w.Write(req.Body)
 | 
						|
			n += m
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// Receive will fill this MCRequest with the data from a reader.
 | 
						|
func (req *MCRequest) Receive(r io.Reader, hdrBytes []byte) (int, error) {
 | 
						|
	if len(hdrBytes) < HDR_LEN {
 | 
						|
		hdrBytes = []byte{
 | 
						|
			0, 0, 0, 0, 0, 0, 0, 0,
 | 
						|
			0, 0, 0, 0, 0, 0, 0, 0,
 | 
						|
			0, 0, 0, 0, 0, 0, 0, 0}
 | 
						|
	}
 | 
						|
	n, err := io.ReadFull(r, hdrBytes)
 | 
						|
	if err != nil {
 | 
						|
		return n, err
 | 
						|
	}
 | 
						|
 | 
						|
	if hdrBytes[0] != RES_MAGIC && hdrBytes[0] != REQ_MAGIC {
 | 
						|
		return n, fmt.Errorf("bad magic: 0x%02x", hdrBytes[0])
 | 
						|
	}
 | 
						|
 | 
						|
	klen := int(binary.BigEndian.Uint16(hdrBytes[2:]))
 | 
						|
	elen := int(hdrBytes[4])
 | 
						|
	// Data type at 5
 | 
						|
	req.DataType = uint8(hdrBytes[5])
 | 
						|
 | 
						|
	req.Opcode = CommandCode(hdrBytes[1])
 | 
						|
	// Vbucket at 6:7
 | 
						|
	req.VBucket = binary.BigEndian.Uint16(hdrBytes[6:])
 | 
						|
	totalBodyLen := int(binary.BigEndian.Uint32(hdrBytes[8:]))
 | 
						|
 | 
						|
	req.Opaque = binary.BigEndian.Uint32(hdrBytes[12:])
 | 
						|
	req.Cas = binary.BigEndian.Uint64(hdrBytes[16:])
 | 
						|
 | 
						|
	if totalBodyLen > 0 {
 | 
						|
		buf := make([]byte, totalBodyLen)
 | 
						|
		m, err := io.ReadFull(r, buf)
 | 
						|
		n += m
 | 
						|
		if err == nil {
 | 
						|
			if req.Opcode >= TAP_MUTATION &&
 | 
						|
				req.Opcode <= TAP_CHECKPOINT_END &&
 | 
						|
				len(buf) > 1 {
 | 
						|
				// In these commands there is "engine private"
 | 
						|
				// data at the end of the extras.  The first 2
 | 
						|
				// bytes of extra data give its length.
 | 
						|
				elen += int(binary.BigEndian.Uint16(buf))
 | 
						|
			}
 | 
						|
 | 
						|
			req.Extras = buf[0:elen]
 | 
						|
			req.Key = buf[elen : klen+elen]
 | 
						|
 | 
						|
			// get the length of extended metadata
 | 
						|
			extMetaLen := 0
 | 
						|
			if elen > 29 {
 | 
						|
				extMetaLen = int(binary.BigEndian.Uint16(req.Extras[28:30]))
 | 
						|
			}
 | 
						|
 | 
						|
			bodyLen := totalBodyLen - klen - elen - extMetaLen
 | 
						|
			if bodyLen > MaxBodyLen {
 | 
						|
				return n, fmt.Errorf("%d is too big (max %d)",
 | 
						|
					bodyLen, MaxBodyLen)
 | 
						|
			}
 | 
						|
 | 
						|
			req.Body = buf[klen+elen : klen+elen+bodyLen]
 | 
						|
			req.ExtMeta = buf[klen+elen+bodyLen:]
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return n, err
 | 
						|
}
 |