Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
4.6 KiB
197 lines
4.6 KiB
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 |
|
}
|
|
|