|
@@ -0,0 +1,242 @@
|
|
|
|
+package rest_websocket
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "bytes"
|
|
|
|
+ "io"
|
|
|
|
+ "time"
|
|
|
|
+
|
|
|
|
+ "git.ali33.ru/fcg-xvii/go-tools/json"
|
|
|
|
+ "git.ali33.ru/fcg-xvii/rest"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+// Int64ToBytes упаковывает int64 в срез байтов заданной длины
|
|
|
|
+func Int64ToBytes(num int64, byteCount int) []byte {
|
|
|
|
+ bytes := make([]byte, byteCount)
|
|
|
|
+ for i := 0; i < byteCount; i++ {
|
|
|
|
+ shift := uint((byteCount - 1 - i) * 8)
|
|
|
|
+ bytes[i] = byte(num >> shift)
|
|
|
|
+ }
|
|
|
|
+ return bytes
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// BytesToInt64 конвертирует срез байтов в int64
|
|
|
|
+func BytesToInt64(bytes []byte) int64 {
|
|
|
|
+ var num int64
|
|
|
|
+ for _, b := range bytes {
|
|
|
|
+ num = (num << 8) | int64(b)
|
|
|
|
+ }
|
|
|
|
+ return num
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func ioError(field string, err error) rest.IErrorArgs {
|
|
|
|
+ return rest.NewError(
|
|
|
|
+ "ErrIO",
|
|
|
|
+ json.Map{
|
|
|
|
+ "field": field,
|
|
|
|
+ "error": err.Error(),
|
|
|
|
+ },
|
|
|
|
+ )
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func ReadMessage(r io.Reader) (*Message, rest.IErrorArgs) {
|
|
|
|
+ // todo
|
|
|
|
+ // id
|
|
|
|
+ sType := make([]byte, 1)
|
|
|
|
+ if _, err := r.Read(sType); err != nil {
|
|
|
|
+ return nil, ioError("type", err)
|
|
|
|
+ }
|
|
|
|
+ mes := Message{
|
|
|
|
+ mType: rest.RequestType(sType[0]),
|
|
|
|
+ }
|
|
|
|
+ if mes.mType == rest.RequestTypeMessage || mes.mType == rest.RequestTypeAnswer {
|
|
|
|
+ sID := make([]byte, 2)
|
|
|
|
+ if _, err := r.Read(sID); err != nil {
|
|
|
|
+ return nil, ioError("id", err)
|
|
|
|
+ }
|
|
|
|
+ mes.id = BytesToInt64(sID)
|
|
|
|
+ }
|
|
|
|
+ if mes.mType == rest.RequestTypeMessage {
|
|
|
|
+ sTimeout := make([]byte, 8)
|
|
|
|
+ if _, err := r.Read(sTimeout); err != nil {
|
|
|
|
+ return nil, ioError("timeout", err)
|
|
|
|
+ }
|
|
|
|
+ mes.timeout = time.Unix(BytesToInt64(sTimeout), 0)
|
|
|
|
+ }
|
|
|
|
+ sCommandSize := make([]byte, 2)
|
|
|
|
+ if _, err := r.Read(sCommandSize); err != nil {
|
|
|
|
+ return nil, ioError("data_size", err)
|
|
|
|
+ }
|
|
|
|
+ if BytesToInt64(sCommandSize) > 0 {
|
|
|
|
+ sCommand := make([]byte, BytesToInt64(sCommandSize))
|
|
|
|
+ if _, err := r.Read(sCommand); err != nil {
|
|
|
|
+ return nil, ioError("data", err)
|
|
|
|
+ }
|
|
|
|
+ mes.command = string(sCommand)
|
|
|
|
+ }
|
|
|
|
+ sDataSize := make([]byte, 8)
|
|
|
|
+ if _, err := r.Read(sDataSize); err != nil {
|
|
|
|
+ return nil, ioError("data_size", err)
|
|
|
|
+ }
|
|
|
|
+ sData := make([]byte, BytesToInt64(sDataSize))
|
|
|
|
+ if _, err := r.Read(sData); err != nil {
|
|
|
|
+ return nil, ioError("data", err)
|
|
|
|
+ }
|
|
|
|
+ if len(sData) > 0 {
|
|
|
|
+ if err := json.Unmarshal(sData, &mes.data); err != nil {
|
|
|
|
+ return nil, ioError("data", err)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ sFilesCount := make([]byte, 2)
|
|
|
|
+ if _, err := r.Read(sFilesCount); err != nil {
|
|
|
|
+ return nil, ioError("files_count", err)
|
|
|
|
+ }
|
|
|
|
+ filesCount := BytesToInt64(sFilesCount)
|
|
|
|
+ files := make(map[string]rest.IReadCloserLen)
|
|
|
|
+ for i := 0; i < int(filesCount); i++ {
|
|
|
|
+ sFileNameLen := make([]byte, 2)
|
|
|
|
+ if _, err := r.Read(sFileNameLen); err != nil {
|
|
|
|
+ return nil, ioError("file_name_length", err)
|
|
|
|
+ }
|
|
|
|
+ sFileName := make([]byte, BytesToInt64(sFileNameLen))
|
|
|
|
+ if _, err := r.Read(sFileName); err != nil {
|
|
|
|
+ return nil, ioError("file_name", err)
|
|
|
|
+ }
|
|
|
|
+ sFileSize := make([]byte, 8)
|
|
|
|
+ if _, err := r.Read(sFileSize); err != nil {
|
|
|
|
+ return nil, ioError("file_size", err)
|
|
|
|
+ }
|
|
|
|
+ fileSize := BytesToInt64(sFileSize)
|
|
|
|
+ if fileSize < 1024*1024 {
|
|
|
|
+ // RAM buffer
|
|
|
|
+ sFileData := make([]byte, fileSize)
|
|
|
|
+ if _, err := r.Read(sFileData); err != nil {
|
|
|
|
+ return nil, ioError("file_data", err)
|
|
|
|
+ }
|
|
|
|
+ buf := rest.NewReadCloserLen(
|
|
|
|
+ io.NopCloser(bytes.NewBuffer(sFileData)),
|
|
|
|
+ int64(len(sFileData)),
|
|
|
|
+ )
|
|
|
|
+ files[string(sFileName)] = buf
|
|
|
|
+ } else {
|
|
|
|
+ // temporary file
|
|
|
|
+ tmpF, err := rest.NewTemporaryFile(fileSize, r)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ files[string(sFileName)] = tmpF
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return &mes, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func NewMessage(command string, data json.Map, files map[string]rest.IReadCloserLen, timeout time.Duration, mType rest.RequestType) *Message {
|
|
|
|
+ return &Message{
|
|
|
|
+ command: command,
|
|
|
|
+ data: data,
|
|
|
|
+ files: files,
|
|
|
|
+ mType: mType,
|
|
|
|
+ timeout: time.Now().Add(timeout),
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type Message struct {
|
|
|
|
+ id int64
|
|
|
|
+ command string
|
|
|
|
+ mType rest.RequestType
|
|
|
|
+ timeout time.Time
|
|
|
|
+ data json.Map
|
|
|
|
+ files map[string]rest.IReadCloserLen
|
|
|
|
+ owner *WebSocket
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (s *Message) Data() json.Map {
|
|
|
|
+ return s.data
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (s *Message) File(name string) (rest.IReadCloserLen, bool) {
|
|
|
|
+ file, check := s.files[name]
|
|
|
|
+ return file, check
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (s *Message) FileKeys() []string {
|
|
|
|
+ keys := make([]string, 0, len(s.files))
|
|
|
|
+ for k := range s.files {
|
|
|
|
+ keys = append(keys, k)
|
|
|
|
+ }
|
|
|
|
+ return keys
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (s *Message) Command() string {
|
|
|
|
+ return s.command
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (s *Message) IsBinagy() bool {
|
|
|
|
+ return len(s.files) > 0
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (s *Message) Write(w io.Writer) rest.IErrorArgs {
|
|
|
|
+ // id
|
|
|
|
+ if _, err := w.Write(Int64ToBytes(s.id, 2)); err != nil {
|
|
|
|
+ return ioError("id", err)
|
|
|
|
+ }
|
|
|
|
+ // type
|
|
|
|
+ if _, err := w.Write(Int64ToBytes(int64(s.mType), 1)); err != nil {
|
|
|
|
+ return ioError("type", err)
|
|
|
|
+ }
|
|
|
|
+ // timeout
|
|
|
|
+ if _, err := w.Write(Int64ToBytes(s.timeout.Unix(), 8)); err != nil {
|
|
|
|
+ return ioError("timeout", err)
|
|
|
|
+ }
|
|
|
|
+ // command length
|
|
|
|
+ if _, err := w.Write(Int64ToBytes(int64(len(s.command)), 2)); err != nil {
|
|
|
|
+ return ioError("command_length", err)
|
|
|
|
+ }
|
|
|
|
+ // command
|
|
|
|
+ if len(s.command) > 0 {
|
|
|
|
+ if _, err := w.Write([]byte(s.command)); err != nil {
|
|
|
|
+ return ioError("command", err)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // data
|
|
|
|
+ data := s.data.JSON()
|
|
|
|
+ // data size
|
|
|
|
+ if _, err := w.Write(Int64ToBytes(int64(len(data)), 8)); err != nil {
|
|
|
|
+ return ioError("data_size", err)
|
|
|
|
+ }
|
|
|
|
+ // data body
|
|
|
|
+ if _, err := w.Write(data); err != nil {
|
|
|
|
+ return ioError("data_body", err)
|
|
|
|
+ }
|
|
|
|
+ // files count
|
|
|
|
+ filesCount := int64(len(s.files))
|
|
|
|
+ if _, err := w.Write(Int64ToBytes(filesCount, 2)); err != nil {
|
|
|
|
+ return ioError("files_count", err)
|
|
|
|
+ }
|
|
|
|
+ // files
|
|
|
|
+ for name, file := range s.files {
|
|
|
|
+ // file name size
|
|
|
|
+ fileNameSize := int64(len(name))
|
|
|
|
+ if _, err := w.Write(Int64ToBytes(fileNameSize, 2)); err != nil {
|
|
|
|
+ return ioError("file_name_size", err)
|
|
|
|
+ }
|
|
|
|
+ // file name
|
|
|
|
+ if _, err := w.Write([]byte(name)); err != nil {
|
|
|
|
+ return ioError("file_name", err)
|
|
|
|
+ }
|
|
|
|
+ // file body size
|
|
|
|
+ if _, err := w.Write(Int64ToBytes(file.Len(), 8)); err != nil {
|
|
|
|
+ return ioError("file_body_size", err)
|
|
|
|
+ }
|
|
|
|
+ // file body
|
|
|
|
+ if _, err := io.Copy(w, file); err != nil {
|
|
|
|
+ return ioError("file_body", err)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (s *Message) Close() {
|
|
|
|
+ for _, file := range s.files {
|
|
|
|
+ file.Close()
|
|
|
|
+ }
|
|
|
|
+}
|