0x4a52466c696e74 1 jaar geleden
bovenliggende
commit
4d41157254
60 gewijzigde bestanden met toevoegingen van 1452 en 1850 verwijderingen
  1. 16 11
      command.go
  2. 3 2
      error.go
  3. 2 2
      example/application.go
  4. 0 340
      example/fox-ar/8th.html
  5. BIN
      example/fox-ar/assets/fonts/Montserrat-Medium.ttf
  6. BIN
      example/fox-ar/assets/fonts/Montserrat-Regular.ttf
  7. BIN
      example/fox-ar/assets/fox_v30.glb
  8. BIN
      example/fox-ar/assets/images/loading-logo.jpg
  9. BIN
      example/fox-ar/assets/images/loading.jpg
  10. BIN
      example/fox-ar/assets/images/loading.png
  11. BIN
      example/fox-ar/assets/images/logo.jpg
  12. BIN
      example/fox-ar/assets/images/og.png
  13. BIN
      example/fox-ar/assets/images/preview-background.jpg
  14. 0 23
      example/fox-ar/change-prompt-on-ios.js
  15. 0 23
      example/fox-ar/components/buttons-handler.js
  16. 0 34
      example/fox-ar/components/customize-buttons-in-capture-preview.js
  17. 0 18
      example/fox-ar/components/disable-culling.js
  18. 0 23
      example/fox-ar/components/fox-lifecycle.js
  19. 0 5
      example/fox-ar/components/hide-capture-button.js
  20. 0 18
      example/fox-ar/components/on-placed-handler.js
  21. 0 34
      example/fox-ar/components/tap-to-place.js
  22. 0 54
      example/fox-ar/index.html
  23. 0 142
      example/fox-ar/styles.css
  24. 0 41
      example/fox-ar/tap-place.js
  25. BIN
      example/fox-ar/tree.glb
  26. 28 30
      example/user.go
  27. 9 9
      example_chat/application.go
  28. 33 0
      example_chat/chat/chat.go
  29. 17 9
      example_chat/chat/user.go
  30. 26 6
      example_chat/users/auth.go
  31. 8 7
      example_chat/users/register.go
  32. 38 14
      example_chat/zv_test.go
  33. 3 4
      go.mod
  34. 3 0
      go.sum
  35. 20 0
      owner.go
  36. 85 85
      request.go
  37. 151 0
      request_stream.go
  38. 22 20
      response.goo
  39. 0 97
      rest_http/request.go
  40. 30 0
      rest_http/request_in.go
  41. 23 51
      rest_http/request_out.go
  42. 24 17
      rest_http/rest.go
  43. 15 0
      rest_websocket/app_config.go
  44. 15 19
      rest_websocket/application.go
  45. 3 62
      rest_websocket/client.go
  46. 0 242
      rest_websocket/message.go
  47. 0 35
      rest_websocket/message_answer.go
  48. 0 22
      rest_websocket/message_event.go
  49. 0 38
      rest_websocket/message_incoming.go
  50. 35 72
      rest_websocket/request.go
  51. 44 0
      rest_websocket/request_wait.go
  52. 0 66
      rest_websocket/response.go
  53. 6 13
      rest_websocket/rest.go
  54. 177 0
      rest_websocket/socket.go
  55. 210 0
      rest_websocket/socket.goo
  56. 123 0
      rest_websocket/socket_server.go
  57. 124 0
      rest_websocket/tools.go
  58. 0 162
      rest_websocket/websocket.go
  59. 36 0
      rest_websocket/z_test.go
  60. 123 0
      tools.go

+ 16 - 11
command.go

@@ -1,17 +1,22 @@
 package rest
 
 type IApplication interface {
-	Executer(r IRequest) (IExecuter, bool)
+	Executer(r IRequestIn) (IExecuter, bool)
+}
+
+type IApplicationStream interface {
+	IApplication
+	Connect() chan<- IStream
 }
 
 ///////////////////////////////////////////////
 
 type IValidator interface {
-	Validate(r IRequest) IResponse
+	Validate(r IRequestIn) IRequestOut
 }
 
 type IExecuter interface {
-	Execute(r IRequest) IResponse
+	Execute(r IRequestIn) IRequestOut
 }
 
 type ICommand interface {
@@ -33,42 +38,42 @@ type Command struct {
 	executer  IExecuter
 }
 
-func (s *Command) Validate(r IRequest) IResponse {
+func (s *Command) Validate(r IRequestIn) IRequestOut {
 	return s.validator.Validate(r)
 }
 
-func (s *Command) Execute(r IRequest) IResponse {
+func (s *Command) Execute(r IRequestIn) IRequestOut {
 	return s.executer.Execute(r)
 }
 
 ///////////////////////////////////////////////
 
-func NewValidator(method func(r IRequest) IResponse) *Validator {
+func NewValidator(method func(r IRequestIn) IRequestOut) *Validator {
 	return &Validator{
 		method,
 	}
 }
 
 type Validator struct {
-	method func(r IRequest) IResponse
+	method func(r IRequestIn) IRequestOut
 }
 
-func (s *Validator) Validate(r IRequest) IResponse {
+func (s *Validator) Validate(r IRequestIn) IRequestOut {
 	return s.method(r)
 }
 
 ///////////////////////////////////////////////
 
-func NewExecuter(method func(r IRequest) IResponse) *Executer {
+func NewExecuter(method func(r IRequestIn) IRequestOut) *Executer {
 	return &Executer{
 		method,
 	}
 }
 
 type Executer struct {
-	method func(r IRequest) IResponse
+	method func(r IRequestIn) IRequestOut
 }
 
-func (s *Executer) Execute(r IRequest) IResponse {
+func (s *Executer) Execute(r IRequestIn) IRequestOut {
 	return s.method(r)
 }

+ 3 - 2
error.go

@@ -64,7 +64,8 @@ func (s *Error) Error() string {
 
 func (s *Error) Map() json.Map {
 	return json.Map{
-		"name": s.name,
-		"args": s.args,
+		"error": true,
+		"name":  s.name,
+		"args":  s.args,
 	}
 }

+ 2 - 2
example/application.go

@@ -34,8 +34,8 @@ func (s *ExampleApp) Secret() []byte {
 	return s.secret
 }
 
-func (s *ExampleApp) Executer(r rest.IRequest) (rest.IExecuter, bool) {
-	switch r.Command() {
+func (s *ExampleApp) Executer(r rest.IRequestIn) (rest.IExecuter, bool) {
+	switch r.RCommand() {
 	case "/user/register":
 		return &ExampleRequestRegister{}, true
 	case "/user/info":

File diff suppressed because it is too large
+ 0 - 340
example/fox-ar/8th.html


BIN
example/fox-ar/assets/fonts/Montserrat-Medium.ttf


BIN
example/fox-ar/assets/fonts/Montserrat-Regular.ttf


BIN
example/fox-ar/assets/fox_v30.glb


BIN
example/fox-ar/assets/images/loading-logo.jpg


BIN
example/fox-ar/assets/images/loading.jpg


BIN
example/fox-ar/assets/images/loading.png


BIN
example/fox-ar/assets/images/logo.jpg


BIN
example/fox-ar/assets/images/og.png


BIN
example/fox-ar/assets/images/preview-background.jpg


+ 0 - 23
example/fox-ar/change-prompt-on-ios.js

@@ -1,23 +0,0 @@
-const changePromptOnIos = (promptBoxInnerHtml, cancelButtonInnetHtml, continueButtonInnerHtml) => {
-    let inDom = false
-    console.log('changePromptOnIos')
-    const observer = new MutationObserver(() => {
-        console.log('MutationObserver')
-        if (document.querySelector('.prompt-box-8w')) {
-            console.log('if prompt-box-8w')
-            if (!inDom) {
-                console.log('inDom')
-                document.querySelector('.prompt-box-8w p').innerHTML = promptBoxInnerHtml
-                document.querySelector('.prompt-button-8w').innerHTML = cancelButtonInnetHtml
-                document.querySelector('.button-primary-8w').innerHTML = continueButtonInnerHtml
-            }
-            inDom = true
-        } else if (inDom) {
-            inDom = false
-            observer.disconnect()
-        }
-    })
-    observer.observe(document.documentElement || document.body, { childList: true, subtree: true })
-}
-
-changePromptOnIos('Для работы AR<br/>необходим доступ к сенсорам<br/>движения', 'Отмена', 'Продолжить')

+ 0 - 23
example/fox-ar/components/buttons-handler.js

@@ -1,23 +0,0 @@
-AFRAME.registerComponent('buttons-handler', {
-    init() {
-        this.fox = document.querySelector('#fox')
-        this.afterExpiriensButtons = document.querySelector('#after-expiriens-buttons')
-        this.repeatButton = document.querySelector('#repeat-button')
-        this.shareButton = document.querySelector('#share-button')
-
-        this.fox.addEventListener('animation-finished', () => {
-            this.afterExpiriensButtons.classList.remove('hidden')
-        })
-
-        this.repeatButton.addEventListener('click', () => {
-            this.afterExpiriensButtons.classList.add('hidden')
-            document.querySelector('#fox-wrapper').setAttribute('tap-to-place', '')
-        })
-
-        this.shareButton.addEventListener('click', () => {
-            navigator.share({
-                url: 'https://fox.mordovia-russia.ru/',
-            })
-        })
-    },
-})

+ 0 - 34
example/fox-ar/components/customize-buttons-in-capture-preview.js

@@ -1,34 +0,0 @@
-const removeChildren = (element) => {
-    if (!element) {
-        return
-    }
-
-    while (element.firstChild) {
-        element.removeChild(element.firstChild)
-    }
-}
-
-AFRAME.registerComponent('customize-buttons-in-capture-preview', {
-    init() {
-        console.log('customizeButtonsInCapturePreviewComponent.init')
-        this.downloadButton = document.querySelector('#downloadButton')
-        this.actionButton = document.querySelector('#actionButton')
-
-        removeChildren(this.downloadButton)
-        removeChildren(this.actionButton)
-
-        this.downloadButton.classList.remove('icon-button')
-        this.downloadButton.classList.add('capture-preview-button')
-        this.actionButton.classList.add('capture-preview-button')
-
-        this.downloadButton.innerHTML = `<svg width="47" height="47" viewBox="0 0 47 47" fill="none" xmlns="http://www.w3.org/2000/svg">
-          <path d="M38.4545 0H34.1818V12.8182C34.1818 13.9975 33.2247 14.9545 32.0455 14.9545H12.8182C11.6389 14.9545 10.6818 13.9975 10.6818 12.8182V0H4.27273C1.91205 0 0 1.91205 0 4.27273V42.7273C0 45.088 1.91205 47 4.27273 47H42.7273C45.088 47 47 45.088 47 42.7273V8.54545L38.4545 0ZM38.4545 42.7273H8.54545V29.9091C8.54545 27.5484 10.4575 25.6364 12.8182 25.6364H34.1818C36.5425 25.6364 38.4545 27.5484 38.4545 29.9091V42.7273Z" fill="white"/>
-          <path d="M29.5931 0H25.2412V10.4444H29.5931V0Z" fill="white"/>
-          </svg>
-          <span>Сохранить</span>`
-        this.actionButton.innerHTML = `<svg width="47" height="47" viewBox="0 0 47 47" fill="none" xmlns="http://www.w3.org/2000/svg">
-          <path fill-rule="evenodd" clip-rule="evenodd" d="M45.0333 11.462C46.2925 10.2028 46.9999 8.49503 46.9999 6.71429C46.9999 4.93355 46.2925 3.22574 45.0333 1.96657C43.7741 0.707397 42.0663 0 40.2856 0C38.5049 0 36.7971 0.707397 35.5379 1.96657C34.2787 3.22574 33.5713 4.93355 33.5713 6.71429C33.5713 7.19067 33.6219 7.66184 33.7202 8.1206L11.7785 19.0915C11.6773 18.9752 11.5717 18.862 11.462 18.7523C10.2028 17.4932 8.49503 16.7858 6.71429 16.7858C4.93355 16.7858 3.22574 17.4932 1.96657 18.7523C0.707397 20.0115 0 21.7193 0 23.5C0 25.2808 0.707397 26.9886 1.96657 28.2478C3.22574 29.5069 4.93355 30.2143 6.71429 30.2143C8.49503 30.2143 10.2028 29.5069 11.462 28.2478C11.5717 28.138 11.6772 28.0249 11.7785 27.9086L33.7202 38.8795C33.6219 39.3382 33.5713 39.8094 33.5713 40.2857C33.5713 42.0664 34.2787 43.7742 35.5379 45.0334C36.7971 46.2926 38.5049 47 40.2856 47C42.0663 47 43.7741 46.2926 45.0333 45.0334C46.2925 43.7742 46.9999 42.0664 46.9999 40.2857C46.9999 38.505 46.2925 36.7972 45.0333 35.538C43.7741 34.2788 42.0663 33.5714 40.2856 33.5714C38.5049 33.5714 36.7971 34.2788 35.5379 35.538C35.4282 35.6476 35.3228 35.7606 35.2216 35.8768L13.2798 24.9059C13.378 24.4473 13.4286 23.9763 13.4286 23.5C13.4286 23.0238 13.378 22.5528 13.2798 22.0942L35.2217 11.1232C35.3229 11.2394 35.4283 11.3524 35.5379 11.462C36.7971 12.7212 38.5049 13.4286 40.2856 13.4286C42.0663 13.4286 43.7741 12.7212 45.0333 11.462Z" fill="white"/>
-          </svg>
-          <span>Поделиться</span>`
-    },
-})

+ 0 - 18
example/fox-ar/components/disable-culling.js

@@ -1,18 +0,0 @@
-AFRAME.registerComponent('disable-culling', {
-    init() {
-        this.el.addEventListener('model-loaded', () => {
-            this.disableCulling()
-        })
-        this.el.addEventListener('model-changed', () => {
-            this.disableCulling()
-        })
-    },
-    disableCulling() {
-        const mesh = this.el.getObject3D('mesh')
-        mesh.traverse((node) => {
-            if (node.isMesh) {
-                node.frustumCulled = false
-            }
-        })
-    },
-})

+ 0 - 23
example/fox-ar/components/fox-lifecycle.js

@@ -1,23 +0,0 @@
-AFRAME.registerComponent('fox-lifecycle', {
-    init() {
-        this.fox = document.querySelector('#fox')
-
-        this.listeners = {
-            startAnimation: this.startAnimation.bind(this),
-        }
-
-        this.fox.addEventListener('start-animation', this.listeners.startAnimation)
-    },
-    startAnimation() {
-        this.fox.setAttribute('visible', 'true')
-        this.fox.setAttribute('animation-mixer', {
-            clip: 'Animation',
-            loop: 'once',
-            clampWhenFinished: true,
-        })
-        this.fox.addEventListener('animation-finished', () => {
-            this.fox.setAttribute('visible', 'false')
-            this.fox.removeAttribute('animation-mixer')
-        })
-    },
-})

+ 0 - 5
example/fox-ar/components/hide-capture-button.js

@@ -1,5 +0,0 @@
-AFRAME.registerComponent('hide-capture-button', {
-    init() {
-        document.querySelector('#recorder').classList.add('hidden')
-    },
-})

+ 0 - 18
example/fox-ar/components/on-placed-handler.js

@@ -1,18 +0,0 @@
-AFRAME.registerComponent('on-placed-handler', {
-    init() {
-        this.fox = document.querySelector('#fox')
-
-        this.listeners = {
-            onPlaced: this.onPlaced.bind(this),
-        }
-
-        this.el.addEventListener('placed', this.listeners.onPlaced)
-    },
-    remove() {
-        this.el.removeEventListener('placed', this.listeners.onPlaced)
-    },
-    onPlaced() {
-        this.fox.emit('start-animation')
-        document.querySelector('#recorder').classList.remove('hidden')
-    },
-})

+ 0 - 34
example/fox-ar/components/tap-to-place.js

@@ -1,34 +0,0 @@
-AFRAME.registerComponent('tap-to-place', {
-    init() {
-        this.raycaster = new THREE.Raycaster()
-        this.camera = document.getElementById('camera')
-        this.threeCamera = this.camera.getObject3D('camera')
-        this.ground = document.getElementById('ground')
-        document.querySelector('#tap-to-place-cursor').setAttribute('visible', 'true')
-        // 2D coordinates of the raycast origin, in normalized device coordinates (NDC)---X and Y
-        // components should be between -1 and 1.  Here we want the cursor in the center of the screen.
-        this.rayOrigin = new THREE.Vector2(0, 0)
-        this.cursorLocation = new THREE.Vector3(0, 0, 0)
-        this.onTap = this.onTap.bind(this)
-        this.el.sceneEl.addEventListener('touchstart', this.onTap)
-    },
-    remove() {
-        this.el.sceneEl.removeEventListener('touchstart', this.onTap)
-    },
-    tick() {
-        this.raycaster.setFromCamera(this.rayOrigin, this.threeCamera)
-        const intersects = this.raycaster.intersectObject(this.ground.object3D, true)
-        if (intersects.length > 0) {
-            const [intersect] = intersects
-            this.cursorLocation = intersect.point
-        }
-        this.el.object3D.position.y = 0.1
-        this.el.object3D.position.lerp(this.cursorLocation, 0.4)
-        this.el.object3D.lookAt(this.threeCamera.position.x, this.el.object3D.position.y, this.threeCamera.position.z)
-    },
-    onTap() {
-        document.querySelector('#tap-to-place-cursor').setAttribute('visible', 'false')
-        this.el.emit('placed')
-        this.el.removeAttribute('tap-to-place')
-    },
-})

File diff suppressed because it is too large
+ 0 - 54
example/fox-ar/index.html


+ 0 - 142
example/fox-ar/styles.css

@@ -1,142 +0,0 @@
-@font-face {
-    font-family: 'Montserrat';
-    src: url('assets/fonts/Montserrat-Regular.ttf') format('truetype');
-    font-weight: 400;
-}
-
-@font-face {
-    font-family: 'Montserrat';
-    src: url('assets/fonts/Montserrat-Medium.ttf') format('truetype');
-    font-weight: 500;
-}
-
-body * {
-    box-sizing: border-box;
-}
-
-body {
-    font-family: 'Montserrat';
-    font-weight: 500;
-}
-
-#requestingCameraIcon {
-    display: none;
-}
-
-#requestingCameraPermissions {
-    display: none;
-}
-
-#loadBackground {
-    background: url('assets/images/loading.png') top left/cover no-repeat;
-}
-
-#loadImage {
-    display: none;
-}
-
-#previewContainer {
-    background: url('assets/images/preview-background.jpg') center/cover no-repeat;
-}
-
-.preview-box {
-    background: #1362b2;
-    background: linear-gradient(180deg, #1362b2 0%, #d20101 100%);
-}
-
-#videoPreview,
-#imagePreview {
-    border-radius: 0;
-    border: 1vmin solid transparent;
-    background: none;
-    max-height: 75vh;
-}
-
-.bottom-bar {
-    height: 15vh;
-}
-
-.logo {
-    position: absolute;
-    z-index: 31;
-    top: 0;
-    left: 0;
-    width: 40vw;
-}
-
-.after-expiriens-buttons {
-    position: absolute;
-    z-index: 1;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    display: grid;
-    grid-template-rows: repeat(3, 1fr);
-    grid-row-gap: 3vh;
-}
-
-.after-expiriens-button {
-    font-size: 5vmin;
-    font-weight: 500;
-    color: #ffffff;
-    text-decoration: none;
-    padding: 2.4vh 8vw;
-    width: 100%;
-    background: #d20101;
-    border: none;
-    display: grid;
-    grid-template-columns: 48px 1fr;
-    grid-gap: 20px;
-    align-items: center;
-}
-
-.after-expiriens-button svg {
-    width: 7vmin;
-    height: 7vmin;
-}
-
-#actionButton {
-    font-family: 'Montserrat';
-    font-size: 4vmin;
-    border-radius: 0;
-    color: #ffffff;
-    background: #d20101;
-}
-
-/* 
-  #downloadButton.capture-preview-button,
-  #actionButton.capture-preview-button {
-    font-family: 'Montserrat';
-    font-size: 4vmin;
-    font-weight: 500;
-    color: #ffffff;
-    padding: 1.5vh 3.5vw;
-    width: 41vw;
-    background: #d20101;
-    display: grid;
-    grid-template-columns: 5vmin 1fr;
-    grid-gap: 16px;
-    align-items: center;
-    position: absolute;
-    top: 50%;
-    transform: translate(0, -50%);
-    border-radius: 0;
-  }
-  
-  .capture-preview-button svg {
-    width: 5vmin;
-    height: 5vmin;
-  } */
-
-.prompt-box-8w {
-    background-color: #ffffff !important;
-    color: #000000 !important;
-}
-
-.prompt-button-8w {
-    background-color: #828398 !important;
-}
-
-.button-primary-8w {
-    background-color: #d20101 !important;
-}

+ 0 - 41
example/fox-ar/tap-place.js

@@ -1,41 +0,0 @@
-// Copyright (c) 2021 8th Wall, Inc.
-/* globals AFRAME */
-
-// Component that places trees where the ground is clicked
-AFRAME.registerComponent('tap-place', {
-  init() {
-    const ground = document.getElementById('ground')
-    ground.addEventListener('click', (event) => {
-      // Create new entity for the new object
-      const newElement = document.createElement('a-entity')
-
-      // The raycaster gives a location of the touch in the scene
-      const touchPoint = event.detail.intersection.point
-      newElement.setAttribute('position', touchPoint)
-
-      const randomYRotation = Math.random() * 360
-      newElement.setAttribute('rotation', `0 ${randomYRotation} 0`)
-
-      newElement.setAttribute('visible', 'false')
-      newElement.setAttribute('scale', '0.0001 0.0001 0.0001')
-
-      newElement.setAttribute('shadow', {
-        receive: false,
-      })
-
-      newElement.setAttribute('gltf-model', '#treeModel')
-      this.el.sceneEl.appendChild(newElement)
-
-      newElement.addEventListener('model-loaded', () => {
-        // Once the model is loaded, we are ready to show it popping in using an animation
-        newElement.setAttribute('visible', 'true')
-        newElement.setAttribute('animation', {
-          property: 'scale',
-          to: '7 7 7',
-          easing: 'easeOutElastic',
-          dur: 800,
-        })
-      })
-    })
-  },
-})

BIN
example/fox-ar/tree.glb


+ 28 - 30
example/user.go

@@ -44,26 +44,23 @@ type ExampleRequestRegister struct {
 	group *ExampleGroup
 }
 
-func (s *ExampleRequestRegister) Validate(req rest.IRequest) rest.IResponse {
+func (s *ExampleRequestRegister) Validate(req rest.IRequestIn) rest.IRequestOut {
 	// пользователь не должен быть авторизован
 	if req.Auth() != nil {
-		return req.ResponseError(
-			500,
-			rest.ErrorMessage("AlreadyAuthorized", "User is already authorized"),
-		)
+		return req.OutError(rest.ErrorMessage("AlreadyAuthorized", "User is already authorized"))
 	}
 	// проверяем имя
 	if s.Name = strings.TrimSpace(s.Name); len(s.Name) == 0 {
-		return req.ResponseError(500, rest.ErrorFiled("name", "expected not empty string"))
+		return req.OutError(rest.ErrorFiled("name", "expected not empty string"))
 	}
 	// проверяем пароль
 	if len(s.Password) < 3 {
-		return req.ResponseError(500, rest.ErrorFiled("password", "expected minimum 3 symbols"))
+		return req.OutError(rest.ErrorFiled("password", "expected minimum 3 symbols"))
 	}
 	// вилидируем группу
 	group, check := App.groups.Load(s.GroupID)
 	if !check {
-		return req.ResponseError(500, rest.ErrorMessage("GroupNotFound", "Group is not found"))
+		return req.OutError(rest.ErrorMessage("GroupNotFound", "Group is not found"))
 	}
 	// Устанавливаем группу для использования в методе выполняния,
 	// чтобы не выбирать её снова
@@ -71,7 +68,7 @@ func (s *ExampleRequestRegister) Validate(req rest.IRequest) rest.IResponse {
 	return nil
 }
 
-func (s *ExampleRequestRegister) Execute(req rest.IRequest) rest.IResponse {
+func (s *ExampleRequestRegister) Execute(req rest.IRequestIn) rest.IRequestOut {
 	// создаем нового юзера
 	userID := App.GenerateID()
 	user := &ExampleUser{
@@ -89,19 +86,20 @@ func (s *ExampleRequestRegister) Execute(req rest.IRequest) rest.IResponse {
 		0,
 	)
 	if err != nil {
-		return req.ResponseError(500, rest.ErrorMessage("TokenGenerateError", err.Error()))
+		return req.OutError(rest.ErrorMessage("TokenGenerateError", err.Error()))
 	}
 
 	files := make(map[string]rest.IReadCloserLen)
 	// сохраняем пользлвателя в хранилище
 	App.users.Store(userID, user)
-	fields, err := rest.Fields(user, files, req.Data().Slice("fields", nil)...)
+	//fields, err := rest.Fields(user, files, req.RData().Slice("fields", nil)...)
+	fields, fErr := req.Fields(user, files)
 	if err != nil {
-		return req.ResponseError(500, rest.ErrorMessage("FieldsError", err.Error()))
+		return req.OutError(fErr)
 	}
 
 	// возвращаем успешный ответ
-	return req.ResponseSuccess(
+	return req.OutSuccess(
 		json.Map{
 			"user":  fields,
 			"token": token,
@@ -116,29 +114,29 @@ type ExampleRequestUserInfo struct {
 	user *ExampleUser
 }
 
-func (s *ExampleRequestUserInfo) Validate(req rest.IRequest) rest.IResponse {
-	if !req.IsAuth() {
-		return req.ResponseError(500, rest.ErrorMessage("NotAuth", "Not authorized"))
+func (s *ExampleRequestUserInfo) Validate(req rest.IRequestIn) rest.IRequestOut {
+	if req.Auth() == nil {
+		return req.OutError(rest.ErrorMessage("NotAuth", "Not authorized"))
 	}
 	auth := req.Auth()
 	user, check := App.users.Load(auth.Int("id", 0))
 	if !check {
-		return req.ResponseError(500, rest.ErrorMessage("UserNotFound", "User not found"))
+		return req.OutError(rest.ErrorMessage("UserNotFound", "User not found"))
 	}
 	s.user = user.(*ExampleUser)
 	return nil
 }
 
-func (s *ExampleRequestUserInfo) Execute(req rest.IRequest) rest.IResponse {
+func (s *ExampleRequestUserInfo) Execute(req rest.IRequestIn) rest.IRequestOut {
 	files := make(map[string]rest.IReadCloserLen)
 	log.Println(s.user.Group)
-	fields := req.Data().Slice("fields", nil)
+	fields := req.RData().Slice("fields", nil)
 	rFields, err := rest.Fields(s.user, files, fields...)
 	log.Println(err)
 	if err != nil {
-		return req.ResponseError(500, err)
+		return req.OutError(err)
 	}
-	return req.ResponseSuccess(rFields, files)
+	return req.OutSuccess(rFields, files)
 }
 
 // set avatar
@@ -148,32 +146,32 @@ type ExampleRequestSetAvatar struct {
 	avatar io.Reader
 }
 
-func (s *ExampleRequestSetAvatar) Validate(req rest.IRequest) rest.IResponse {
+func (s *ExampleRequestSetAvatar) Validate(req rest.IRequestIn) rest.IRequestOut {
 	// проверяем авторизацию
-	if !req.IsAuth() {
-		return req.ResponseError(500, rest.ErrorMessage("NotAuth", "Not authorized"))
+	if req.Auth() == nil {
+		return req.OutError(rest.ErrorMessage("NotAuth", "Not authorized"))
 	}
 	// проверяем файл аватара
 	var check bool
-	if s.avatar, check = req.File("avatar.jpg"); !check {
-		return req.ResponseError(500, rest.ErrorMessage("FileNotFound", "avatar.jpg"))
+	if s.avatar, check = req.RFile("avatar.jpg"); !check {
+		return req.OutError(rest.ErrorMessage("FileNotFound", "avatar.jpg"))
 	}
 	// поиск юзера
 	auth := req.Auth()
 	user, check := App.users.Load(auth.Int("id", 0))
 	if !check {
 		//return rest.ResponseErrorMessage("UserNotFound", "User not found", 500)
-		return req.ResponseError(500, rest.ErrorMessage("UserNotFound", "User not found"))
+		return req.OutError(rest.ErrorMessage("UserNotFound", "User not found"))
 	}
 	s.user = user.(*ExampleUser)
 	return nil
 }
 
-func (s *ExampleRequestSetAvatar) Execute(req rest.IRequest) rest.IResponse {
+func (s *ExampleRequestSetAvatar) Execute(req rest.IRequestIn) rest.IRequestOut {
 	// сохраняем аватар
-	f, _ := req.File("avatar.jpg")
+	f, _ := req.RFile("avatar.jpg")
 	s.user.Avatar, _ = io.ReadAll(f)
 	log.Println("U_Ava", s.user.Avatar)
 	// возвращаем пустой ответ
-	return req.ResponseSuccess(json.Map{}, nil)
+	return req.OutSuccess(json.Map{}, nil)
 }

+ 9 - 9
example_chat/application.go

@@ -1,28 +1,28 @@
 package example_chat_test
 
 import (
-	"context"
 	"log"
 
 	"git.ali33.ru/fcg-xvii/rest"
+	"git.ali33.ru/fcg-xvii/rest/example_chat/chat"
 	"git.ali33.ru/fcg-xvii/rest/example_chat/users"
-	"git.ali33.ru/fcg-xvii/rest/rest_websocket"
 )
 
-func NewApplication(ctx context.Context) *Application {
-	rApp := rest_websocket.NewApplication(ctx)
+func NewApplication(chat *chat.Chat) *Application {
+	//rApp := rest_websocket.NewApplication(ctx)
 	return &Application{
-		Application: rApp,
+		Chat: chat,
+		//Application: rApp,
 	}
 }
 
 type Application struct {
-	*rest_websocket.Application
+	*chat.Chat
 }
 
-func (s *Application) Executer(r rest.IRequest) (rest.IExecuter, bool) {
-	log.Println("COMMAND", r.Command())
-	switch r.Command() {
+func (s *Application) Executer(r rest.IRequestIn) (rest.IExecuter, bool) {
+	log.Println("COMMAND", r.RCommand())
+	switch r.RCommand() {
 	case "/users/register":
 		return &users.Register{}, true
 	}

+ 33 - 0
example_chat/chat/chat.go

@@ -1,21 +1,54 @@
 package chat
 
 import (
+	"context"
+	"log"
 	"sync"
 	"sync/atomic"
+
+	"git.ali33.ru/fcg-xvii/rest"
 )
 
 func New() *Chat {
 	res := &Chat{
 		users:     &sync.Map{},
 		idCounter: &atomic.Int64{},
+		chConnect: make(chan rest.IStream, 5),
 	}
+	go res.work()
 	return res
 }
 
 type Chat struct {
+	ctx       context.Context
+	cancel    context.CancelFunc
 	users     *sync.Map
 	idCounter *atomic.Int64
+	chConnect chan rest.IStream
+}
+
+func (s *Chat) Close() {
+	s.cancel()
+}
+
+func (s *Chat) work() {
+	s.ctx, s.cancel = context.WithCancel(context.Background())
+	for {
+		select {
+		case <-s.ctx.Done():
+			return
+		case stream := <-s.chConnect:
+			log.Println("CONNECT-------")
+			go func() {
+				<-stream.Context().Done()
+				log.Println("DISCONNECT-------")
+			}()
+		}
+	}
+}
+
+func (s *Chat) Connect() chan<- rest.IStream {
+	return s.chConnect
 }
 
 func (s *Chat) Register(name, password string) *User {

+ 17 - 9
example_chat/chat/user.go

@@ -1,16 +1,17 @@
 package chat
 
 import (
+	"time"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
 	"git.ali33.ru/fcg-xvii/rest"
-	"git.ali33.ru/fcg-xvii/rest/rest_websocket"
-	"github.com/fcg-xvii/go-tools/json"
 )
 
 type User struct {
 	ID       int64  `rest:"default"`
 	Name     string `rest:"default"`
 	Password string `rest:"ignore"`
-	Socket   *rest_websocket.WebSocket
+	Streams  []rest.IStream
 }
 
 func (s *User) SendMessage(sender *User, message string, file rest.IReadCloserLen) {
@@ -18,12 +19,19 @@ func (s *User) SendMessage(sender *User, message string, file rest.IReadCloserLe
 	if file != nil {
 		files = make(map[string]rest.IReadCloserLen)
 	}
-	mes := rest_websocket.NewMessage(
-		"/users/message",
-		json.Map{
-			"sender": sender,
-			"message": message,
+	req := rest.NewRequestStream(
+		time.Now().Add(time.Second*5),
+		&rest.Request{
+			Type:    rest.RequestTypeEvent,
+			Command: "/users/message",
+			Data: json.Map{
+				"sender":  sender,
+				"message": message,
+			},
+			Files: files,
 		},
-		map[string]
 	)
+	for _, stream := range s.Streams {
+		stream.SendMessage(req)
+	}
 }

+ 26 - 6
example_chat/users/auth.go

@@ -1,6 +1,9 @@
 package users
 
 import (
+	"log"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
 	"git.ali33.ru/fcg-xvii/rest"
 	"git.ali33.ru/fcg-xvii/rest/example_chat/chat"
 )
@@ -10,17 +13,34 @@ type Auth struct {
 	Password string `rest:"required"`
 }
 
-func (s *Auth) Validate(req rest.IRequest) rest.IResponse {
-	if req.IsAuth() {
-		return req.ResponseError(500, rest.ErrorMessage("ErrAuth", "already auth"))
+func (s *Auth) Validate(req rest.IRequestIn) rest.IRequestOut {
+	log.Println("VALIDATE")
+	if req.Auth() == nil {
+		return req.OutError(rest.ErrorMessage("ErrAuth", "already auth"))
 	}
 	return nil
 }
 
-func (s *Auth) Execute(req rest.IRequest) rest.IResponse {
-	core := req.Core().(*chat.Chat)
+func (s *Auth) Execute(req rest.IRequestIn) rest.IRequestOut {
+	log.Println("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO")
+	core := req.RCore().(*chat.Chat)
 	user, check := core.Auth(s.Name, s.Password)
 	if !check {
-		return req.ResponseError(500, rest.ErrorMessage("ErrAuthData", "user is not found"))
+		return req.OutError(rest.ErrorMessage("ErrAuthData", "user is not found"))
+	}
+	authMap := json.Map{
+		"id": user.ID,
+	}
+	token, err := req.GenerateToken(authMap, 0)
+	if err != nil {
+		return req.OutError(rest.ErrorMessage("ErrTokenGenerate", err.Error()))
 	}
+	req.SetAuth(authMap)
+	return req.OutSuccess(
+		json.Map{
+			"user":  user,
+			"token": token,
+		},
+		nil,
+	)
 }

+ 8 - 7
example_chat/users/register.go

@@ -14,27 +14,28 @@ type Register struct {
 	Password string `rest:"require"`
 }
 
-func (s *Register) Validate(req rest.IRequest) rest.IResponse {
+func (s *Register) Validate(req rest.IRequestIn) rest.IRequestOut {
 	log.Println(s.Name, s.Password)
 	s.Name = strings.TrimSpace(s.Name)
 	if len(s.Name) < 3 {
-		return req.ResponseError(500, rest.ErrorFiled("name", "minimum 3 symbols"))
+		return req.OutError(rest.ErrorFiled("name", "minimum 3 symbols"))
 	}
 	if len(s.Password) < 5 {
-		return req.ResponseError(500, rest.ErrorFiled("name", "minimum 5 symbols"))
+		return req.OutError(rest.ErrorFiled("name", "minimum 5 symbols"))
 	}
 	return nil
 }
 
-func (s *Register) Execute(req rest.IRequest) rest.IResponse {
-	core := req.Core().(*chat.Chat)
+func (s *Register) Execute(req rest.IRequestIn) rest.IRequestOut {
+	core := req.RCore().(*chat.Chat)
 	user := core.Register(s.Name, s.Password)
 	fields, err := rest.Fields(user, nil)
 	if err != nil {
-		return req.ResponseError(500, err)
+		return req.OutError(err)
 	}
 	req.SetAuth(json.Map{
 		"user": user,
 	})
-	return req.ResponseSuccess(fields, nil)
+	//time.Sleep(time.Second * 5)
+	return req.OutSuccess(fields, nil)
 }

+ 38 - 14
example_chat/zv_test.go

@@ -7,7 +7,7 @@ import (
 	"git.ali33.ru/fcg-xvii/go-tools/json"
 	"git.ali33.ru/fcg-xvii/rest"
 	"git.ali33.ru/fcg-xvii/rest/example_chat/chat"
-	"git.ali33.ru/fcg-xvii/rest/rest_websocket"
+	ws "git.ali33.ru/fcg-xvii/rest/rest_websocket"
 )
 
 func TestChat(t *testing.T) {
@@ -22,35 +22,59 @@ func TestChat(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	app := NewApplication(server.Context())
+	app := NewApplication(core)
 
-	restEngine := rest_websocket.New(app, core)
+	restEngine := ws.New(app, core)
 	restEngine.Prepare(server, "/ws")
 
 	//ch := make(chan struct{})
 	//<-ch
 
-	cl, err := rest_websocket.NewClient(clAddr)
+	cl, err := ws.NewClient(clAddr)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	t.Log(cl)
 
-	mes := rest_websocket.NewMessage(
-		"/users/register",
-		json.Map{
-			"name":     "LETO",
-			"password": "my-pass-99",
+	/*
+		mes := rest_websocket.(
+			"/users/register",
+			json.Map{
+				"name":     "LETO",
+				"password": "my-pass-99",
+			},
+			nil,
+			time.Second*10,
+			rest.RequestTypeMessage,
+		)
+	*/
+
+	mes := &rest.RequestStream{
+		Timeout: time.Now().Add(time.Second),
+		Request: &rest.Request{
+			Type:    rest.RequestTypeIn,
+			Command: "/users/register",
+			Data: json.Map{
+				"name":     "LETO",
+				"password": "my-pass-99",
+			},
 		},
-		nil,
-		time.Second*10,
-		rest.RequestTypeMessage,
-	)
+	}
 
-	if err := cl.SendMessage(mes); err != nil {
+	answ, err := cl.SendMessage(mes)
+	if err != nil {
 		t.Fatal(err)
 	}
 
+	resp, ok := <-answ
+	if !ok {
+		t.Log("answer is not received")
+	} else {
+		t.Log("resp", resp.Data)
+	}
+
 	time.Sleep(time.Second * 5)
+	cl.Close()
+	time.Sleep(time.Second * 60)
 }

+ 3 - 4
go.mod

@@ -5,9 +5,8 @@ go 1.20
 require (
 	git.ali33.ru/fcg-xvii/go-tools v0.0.0-20230529104008-2552c5121c91
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
+	github.com/fcg-xvii/go-tools v0.0.0-20220316201232-6d6629b9d1e7
+	github.com/gorilla/websocket v1.5.1
 )
 
-require (
-	github.com/gorilla/websocket v1.5.1 // indirect
-	golang.org/x/net v0.17.0 // indirect
-)
+require golang.org/x/net v0.17.0 // indirect

+ 3 - 0
go.sum

@@ -2,7 +2,10 @@ git.ali33.ru/fcg-xvii/go-tools v0.0.0-20230529104008-2552c5121c91 h1:8N3j1V1Yx24
 git.ali33.ru/fcg-xvii/go-tools v0.0.0-20230529104008-2552c5121c91/go.mod h1:YbBhWFFNNQIKcRisQFnpVaN5KA+XHGImSU1Z/MuntqU=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/fcg-xvii/go-tools v0.0.0-20220316201232-6d6629b9d1e7 h1:y9HN4JU8mMzR6KfCRQVO7V5CUJqrCe55fDYreyur7YU=
+github.com/fcg-xvii/go-tools v0.0.0-20220316201232-6d6629b9d1e7/go.mod h1:iqeLyAqB+RN0zxQTNk7yWI9dzy09Oc6yUk1m1vkdws0=
 github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
 github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
+github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
 golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=

+ 20 - 0
owner.go

@@ -0,0 +1,20 @@
+package rest
+
+import (
+	"context"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
+)
+
+type IOwner interface {
+	IsStream() bool
+}
+
+type IStream interface {
+	SetAuth(json.Map)
+	Auth() json.Map
+	SetClientData(key string, val any)
+	ClientData(key string) (any, bool)
+	Context() context.Context
+	SendMessage(IRequestOut) (<-chan *RequestStream, error)
+}

+ 85 - 85
request.go

@@ -6,135 +6,135 @@ import (
 	"git.ali33.ru/fcg-xvii/go-tools/json"
 )
 
-type RequestType byte
+type (
+	RequestType         byte
+	RequestFiles        map[string]IReadCloserLen
+	TokenGenerateMethod func(data json.Map, expire int64) (string, error)
+)
 
 const (
-	RequestTypeMessage RequestType = iota
+	RequestTypeIn RequestType = iota
+	RequestTypeOut
 	RequestTypeEvent
-	RequestTypeAnswer
 )
 
 type IRequest interface {
-	Type() RequestType
-	IsAuth() bool
-	SetAuth(auth json.Map)
-	Command() string
-	Auth() json.Map
-	Data() json.Map
-	FileKeys() []string
-	File(name string) (IReadCloserLen, bool)
-	GenerateToken(data json.Map, expire int64) (string, error)
-	Root() any
-	Core() any
-	ResponseSuccess(data json.Map, files map[string]IReadCloserLen) IResponse
-	ResponseError(code int, err IErrorArgs) IResponse
-	Close()
-}
-
-type Request struct {
-	rType         RequestType
-	auth          json.Map
-	command       string
-	data          json.Map
-	files         map[string]IReadCloserLen
-	core          any
-	root          any
-	generateToken func(data json.Map, expire int64) (string, error)
-	//Response      func(data json.Map, files map[string]io.ReadCloser) IResponse
+	RType() RequestType
+	RCommand() string
+	RData() json.Map
+	RFiles() RequestFiles
+	RFile(string) (IReadCloserLen, bool)
+	RClose()
+	Fields(any, RequestFiles) (json.Map, IErrorArgs)
 }
 
-// сеттеры
+////////////////////////////////////////////
 
-func (s *Request) SetType(rType RequestType) {
-	s.rType = rType
+type Request struct {
+	Type    RequestType
+	Command string
+	Data    json.Map
+	Files   RequestFiles
 }
 
-func (s *Request) SetAuth(auth json.Map) {
-	s.auth = auth
+func (s *Request) RType() RequestType {
+	return s.Type
 }
 
-func (s *Request) SetCommand(command string) {
-	s.command = command
+func (s *Request) RCommand() string {
+	return s.Command
 }
 
-func (s *Request) SetData(data json.Map) {
-	s.data = data
+func (s *Request) RData() json.Map {
+	if s.Data == nil {
+		return json.Map{}
+	}
+	return s.Data
 }
 
-func (s *Request) SetFiles(files map[string]IReadCloserLen) {
-	s.files = files
+func (s *Request) RFiles() RequestFiles {
+	return s.Files
 }
 
-func (s *Request) SetRoot(root any) {
-	s.root = root
+func (s *Request) RFile(name string) (IReadCloserLen, bool) {
+	res, check := s.Files[name]
+	return res, check
 }
 
-func (s *Request) SetCore(core any) {
-	s.core = core
+func (s *Request) RClose() {
+	for _, file := range s.Files {
+		file.Close()
+	}
+	s.Files = nil
 }
 
-func (s *Request) SetGenerateToken(generateToken func(data json.Map, expire int64) (string, error)) {
-	s.generateToken = generateToken
+func (s *Request) Fields(obj any, files RequestFiles) (json.Map, IErrorArgs) {
+	return Fields(obj, files, s.Data.Slice("fields", nil)...)
 }
 
-// методы интерфейса
+////////////////////////////////////////////////////////////
 
-func (s *Request) Type() RequestType {
-	return RequestTypeMessage
+type IRequestIn interface {
+	IRequest
+	Auth() json.Map
+	SetAuth(json.Map)
+	GenerateToken(data json.Map, expire int64) (string, error)
+	ROwner() IOwner
+	RCore() any
+	OutSuccess(data json.Map, files RequestFiles) IRequestOut
+	OutError(err IErrorArgs) IRequestOut
+	ClientData(key string) (any, bool)
+	SetClientData(key string, data any)
 }
 
-func (s *Request) IsAuth() bool {
-	return s.auth != nil
+type RequestIn struct {
+	IRequest
+	Owner          IOwner
+	Core           any
+	GeneratorToken TokenGenerateMethod
 }
 
-func (s *Request) Command() string {
-	return s.command
+func (s *RequestIn) Auth() json.Map {
+	if store, check := s.Owner.(IStream); check {
+		return store.Auth()
+	}
+	return nil
 }
 
-func (s *Request) Auth() json.Map {
-	return s.auth
+func (s *RequestIn) SetAuth(auth json.Map) {
+	if store, check := s.Owner.(IStream); check {
+		store.SetAuth(auth)
+	}
 }
 
-func (s *Request) Data() json.Map {
-	return s.data
+func (s *RequestIn) GenerateToken(data json.Map, expire int64) (string, error) {
+	return s.GeneratorToken(data, expire)
 }
 
-func (s *Request) FileKeys() []string {
-	keys := make([]string, 0, len(s.files))
-	for key := range s.files {
-		keys = append(keys, key)
+func (s *RequestIn) ClientData(key string) (any, bool) {
+	if store, check := s.Owner.(IStream); check {
+		return store.ClientData(key)
 	}
-	return keys
-}
-
-func (s *Request) File(name string) (io.Reader, bool) {
-	file, ok := s.files[name]
-	return file, ok
+	return nil, false
 }
 
-func (s *Request) GenerateToken(data json.Map, expire int64) (string, error) {
-	return s.generateToken(data, expire)
+func (s *RequestIn) SetClientData(key string, data any) {
+	if store, check := s.Owner.(IStream); check {
+		store.SetClientData(key, data)
+	}
 }
 
-func (s *Request) Root() any {
-	return s.root
+func (s *RequestIn) ROwner() IOwner {
+	return s.Owner
 }
 
-func (s *Request) Core() any {
-	return s.core
+func (s *RequestIn) RCore() any {
+	return s.Core
 }
 
-func (s *Request) ResponseSuccess(data json.Map, files map[string]IReadCloserLen) IResponse {
-	return &Response{
-		code:  200,
-		data:  data,
-		files: files,
-	}
-}
+/////////////////////////////////////////////////////////
 
-func (s *Request) ResponseError(code int, err IErrorArgs) IResponse {
-	return &Response{
-		code: code,
-		err:  err,
-	}
+type IRequestOut interface {
+	IRequest
+	Write(io.Writer) IErrorArgs
 }

+ 151 - 0
request_stream.go

@@ -0,0 +1,151 @@
+package rest
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"time"
+)
+
+func NewRequestStream(timeout time.Time, req *Request) *RequestStream {
+	return &RequestStream{
+		Request: req,
+		Timeout: timeout,
+	}
+}
+
+func ReadRequestStream(r io.Reader) (*RequestStream, IErrorArgs) {
+	// type
+	req := RequestStream{
+		Request: &Request{},
+	}
+	if err := ReadByte(r, "type", (*byte)(&req.Type)); err != nil {
+		return nil, err
+	}
+	if req.Type == RequestTypeIn || req.Type == RequestTypeOut {
+		// id
+		if err := ReadInt64(r, 2, "id", &req.ID); err != nil {
+			return nil, err
+		}
+		if req.Type == RequestTypeIn {
+			// timeout
+			var timeout int64
+			if err := ReadInt64(r, 8, "timeout", &timeout); err != nil {
+				return nil, err
+			}
+			req.Timeout = time.Unix(timeout, 0)
+		}
+	}
+	// command
+	if err := ReadString(r, 2, "command", &req.Command); err != nil {
+		return nil, err
+	}
+	// data
+	dataBuf, err := ReadBufSize(r, 8, "data")
+	if err != nil {
+		return nil, err
+	}
+	if len(dataBuf) > 0 {
+		if err := json.Unmarshal(dataBuf, &req.Data); err != nil {
+			return nil, ioError("data", err)
+		}
+	}
+	// files count
+	var filesCount int64
+	if err := ReadInt64(r, 2, "files_count", &filesCount); err != nil {
+		return nil, err
+	}
+	// files
+	if filesCount > 0 {
+		req.Files = make(RequestFiles)
+
+	}
+	for i := 0; i < int(filesCount); i++ {
+		filePrefix := fmt.Sprintf("file[%v]", i)
+		var fileName string
+		if err := ReadString(r, 2, filePrefix+".name", &fileName); err != nil {
+			return nil, err
+		}
+		var fileSize int64
+		if err := ReadInt64(r, 8, filePrefix+".size", &fileSize); err != nil {
+			return nil, err
+		}
+		if fileSize < 1024*1024 {
+			// RAM
+			fileData, err := ReadBuf(r, int(fileSize), filePrefix+".data")
+			if err != nil {
+				return nil, err
+			}
+			buf := NewReadCloserLen(
+				io.NopCloser(bytes.NewBuffer(fileData)),
+				int64(len(fileData)),
+			)
+			req.Files[fileName] = buf
+		} else {
+			// временной файл
+			tmpF, err := NewTemporaryFile(fileSize, r)
+			if err != nil {
+				return nil, err
+			}
+			req.Files[fileName] = tmpF
+		}
+	}
+	return &req, nil
+}
+
+////////////////////////////////////////
+
+type RequestStream struct {
+	ID      int64
+	Timeout time.Time
+	*Request
+}
+
+func (s *RequestStream) Write(w io.Writer) IErrorArgs {
+	// type
+	if err := WriteByte(w, byte(s.Type), "type"); err != nil {
+		return err
+	}
+	if s.Type == RequestTypeIn || s.Type == RequestTypeOut {
+		// id
+		if err := WriteInt64(w, s.ID, 2, "id"); err != nil {
+			return err
+		}
+		if s.Type == RequestTypeIn {
+			// timeout
+			if err := WriteInt64(w, s.Timeout.Unix(), 8, "timeout"); err != nil {
+				return err
+			}
+		}
+	}
+	// command
+	if err := WriteString(w, s.Command, "command", 2); err != nil {
+		return err
+	}
+	// data
+	if err := WriteBufSize(w, s.Data.JSON(), 8, "data"); err != nil {
+		return err
+	}
+	// files count
+	if err := WriteInt64(w, int64(len(s.Files)), 2, "files_count"); err != nil {
+		return err
+	}
+	// files
+	for name, file := range s.Files {
+		prefix := fmt.Sprintf("file[%s]", name)
+		// file name
+		if err := WriteString(w, name, prefix+".name", 2); err != nil {
+			return err
+		}
+		// file body size
+		if err := WriteInt64(w, file.Len(), 8, prefix+".size"); err != nil {
+			return err
+		}
+		// file body
+		if _, err := io.Copy(w, file); err != nil {
+			return ioError(prefix+".data", err)
+		}
+	}
+	return nil
+}

+ 22 - 20
response.go → response.goo

@@ -1,9 +1,6 @@
 package rest
 
-import (
-	"git.ali33.ru/fcg-xvii/go-tools/json"
-)
-
+/*
 type IResponse interface {
 	IsError() bool
 	KeySet(key string, val any)
@@ -12,47 +9,52 @@ type IResponse interface {
 	Send(writer any) IErrorArgs
 }
 
+func NewResponse() *Response {
+	return &Response{
+		RCode:  200,
+		RData:  make(json.Map),
+		RFiles: make(map[string]IReadCloserLen),
+	}
+}
+
 type Response struct {
-	code  int
-	err   IErrorArgs
-	data  json.Map
-	files map[string]IReadCloserLen
+	RCode  int
+	RErr   IErrorArgs
+	RData  json.Map
+	RFiles map[string]IReadCloserLen
 }
 
 func (s *Response) IsError() bool {
-	return s.code != 200
+	return s.RCode != 200
 }
 
 func (s *Response) KeySet(key string, val any) {
-	s.data[key] = val
+	s.RData[key] = val
 }
 
 func (s *Response) FileSet(name string, file IReadCloserLen) {
-	s.files[name] = file
+	s.RFiles[name] = file
 }
 
 func (s *Response) Close() {
-	for _, file := range s.files {
+	for _, file := range s.RFiles {
 		file.Close()
 	}
 }
 
-func (s *Response) Send(writer any) IErrorArgs {
-	return nil
-}
-
 func (s *Response) Code() int {
-	return s.code
+	return s.RCode
 }
 
 func (s *Response) Data() json.Map {
-	return s.data
+	return s.RData
 }
 
 func (s *Response) Files() map[string]IReadCloserLen {
-	return s.files
+	return s.RFiles
 }
 
 func (s *Response) Err() IErrorArgs {
-	return s.err
+	return s.RErr
 }
+*/

+ 0 - 97
rest_http/request.go

@@ -1,97 +0,0 @@
-package rest_http
-
-import (
-	"net/http"
-
-	"git.ali33.ru/fcg-xvii/go-tools/json"
-	"git.ali33.ru/fcg-xvii/rest"
-)
-
-// Request реализует объект запроса
-type Request struct {
-	*http.Request
-	core           any
-	auth           json.Map
-	data           json.Map
-	files          map[string]rest.IReadCloserLen
-	tokenGenerator func(json.Map, int64) (string, error)
-}
-
-func (s *Request) Type() rest.RequestType {
-	return rest.RequestTypeMessage
-}
-
-func (s *Request) Root() any {
-	return s.Request
-}
-
-func (s *Request) Core() any {
-	return s.core
-}
-
-// GenerateToken создает новый токен авторизации. expire - timestamp даты, после которой токен не будет действителен (если указан 0 - токен бессрочный)
-func (s *Request) GenerateToken(data json.Map, expire int64) (string, error) {
-	return s.tokenGenerator(data, expire)
-}
-
-// Возвращает путь (краткий url) запроса
-func (s *Request) Command() string {
-	return s.URL.Path
-}
-
-// Data возвращает словерь json запроса
-func (s *Request) Data() json.Map {
-	return s.data
-}
-
-// FileKeys возвращает все имена файлы, которые доступны в запросе
-func (s *Request) FileKeys() []string {
-	res := make([]string, 0, len(s.files))
-	for k := range s.files {
-		res = append(res, k)
-	}
-	return res
-}
-
-// File возвращает файл, принятый в запросе multipart/form-data, если он существует
-func (s *Request) File(name string) (rest.IReadCloserLen, bool) {
-	r, check := s.files[name]
-	return r, check
-}
-
-// Auth возвращает словарь с данными об авторизации (при условии, что в заголовке Bearer указан токен и его удалось успешно расшифровать и распарсить)
-func (s *Request) Auth() json.Map {
-	return s.auth
-}
-
-// IsAuth указывает, есть ли словарь авторизации или нет
-func (s *Request) IsAuth() bool {
-	return s.auth != nil
-}
-
-func (s *Request) SetAuth(json.Map) {}
-
-// IsJSON возарвщвет true, если в заголовке ContentType application/json
-func (s *Request) IsJSON() bool {
-	return s.Header.Get("Content-Type") == "application/json"
-}
-
-// // IsForm возарвщвет true, если в заголовке ContentType multipart/form-data
-func (s *Request) IsForm() bool {
-	return s.Header.Get("Content-Type") == "application/x-www-form-urlencoded"
-}
-
-// Close закрывает все открытые ресурсы запоса (файлы)
-func (s *Request) Close() {
-	for _, file := range s.files {
-		file.Close()
-	}
-}
-
-func (s *Request) ResponseSuccess(data json.Map, files map[string]rest.IReadCloserLen) rest.IResponse {
-	return ResponseSuccess(data, files)
-}
-
-func (s *Request) ResponseError(code int, err rest.IErrorArgs) rest.IResponse {
-	return ResponseError(code, err)
-}

+ 30 - 0
rest_http/request_in.go

@@ -0,0 +1,30 @@
+package rest_http
+
+import (
+	"git.ali33.ru/fcg-xvii/go-tools/json"
+	"git.ali33.ru/fcg-xvii/rest"
+)
+
+type RequestIn struct {
+	*rest.RequestIn
+	auth json.Map
+}
+
+func (s *RequestIn) IsAuth() bool {
+	return s.auth != nil
+}
+
+func (s *RequestIn) Auth() json.Map {
+	return s.auth
+}
+
+func (s *RequestIn) OutSuccess(data json.Map, files rest.RequestFiles) rest.IRequestOut {
+	return ResponseSuccess(&rest.Request{
+		Data:  data,
+		Files: files,
+	})
+}
+
+func (s *RequestIn) OutError(err rest.IErrorArgs) rest.IRequestOut {
+	return ResponseError(err)
+}

+ 23 - 51
rest_http/response.go → rest_http/request_out.go

@@ -4,64 +4,39 @@ import (
 	"bytes"
 	"encoding/json"
 	"io"
-	"log"
 	"mime/multipart"
 	"net/http"
 	"net/textproto"
 
-	mjson "git.ali33.ru/fcg-xvii/go-tools/json"
 	"git.ali33.ru/fcg-xvii/rest"
 )
 
-func NewResponse() *Response {
-	return &Response{
-		data:  make(mjson.Map),
-		files: make(map[string]rest.IReadCloserLen),
+func NewRequestOut(req rest.IRequest) *RequestOut {
+	return &RequestOut{
+		IRequest: req,
 	}
 }
 
 // Response реализует объект ответа
-type Response struct {
-	data  mjson.Map
-	files map[string]rest.IReadCloserLen
-	err   rest.IErrorArgs
-	code  int
-}
-
-func (s *Response) IsError() bool {
-	return s.err != nil
-}
-
-// KeySet устанавливает значение в словаре ответа по ключу
-func (s *Response) KeySet(key string, val any) {
-	s.data[key] = val
-}
-
-// FileSet устанавливает файл в словаре файлов по ключу
-func (s *Response) FileSet(name string, file rest.IReadCloserLen) {
-	s.files[name] = file
-}
-
-// Close закрывает ресурсы ответа после завершения отдачи серверу
-func (s *Response) Close() {
-	for _, file := range s.files {
-		file.Close()
-	}
+type RequestOut struct {
+	Err rest.IErrorArgs
+	rest.IRequest
 }
 
 // Send отправляет запрос серверу
-func (s *Response) Send(writer any) rest.IErrorArgs {
+func (s *RequestOut) Write(writer io.Writer) rest.IErrorArgs {
 	w := writer.(http.ResponseWriter)
-	log.Println("SEND...")
-	defer s.Close()
-	if s.IsError() {
+	//log.Println("SEND...")
+	defer s.RClose()
+	if s.Err != nil {
 		w.Header().Set("Content-Type", "application/json")
-		w.WriteHeader(s.code)
-		w.Write([]byte(s.err.Error()))
+		w.WriteHeader(500)
+		w.Write([]byte(s.Err.Map().JSON()))
 		return nil
 	}
 	// Если есть файлы, то используем multipart
-	if len(s.files) > 0 {
+	files := s.RFiles()
+	if len(files) > 0 {
 		var b bytes.Buffer
 		writer := multipart.NewWriter(&b)
 		w.Header().Set("Content-Type", writer.FormDataContentType())
@@ -74,12 +49,12 @@ func (s *Response) Send(writer any) rest.IErrorArgs {
 		if err != nil {
 			return rest.ErrorMessage("ErrResponsePartDataCreate", err.Error())
 		}
-		if err := json.NewEncoder(dataPart).Encode(s.data); err != nil {
+		if err := json.NewEncoder(dataPart).Encode(s.RData()); err != nil {
 			return rest.ErrorMessage("ErrResponseDataJsonEncode", err.Error())
 		}
 
 		// Добавляем файлы
-		for filename, file := range s.files {
+		for filename, file := range files {
 			part, err := writer.CreateFormFile("file", filename)
 			if err != nil {
 				return rest.ErrorMessage("ErrResponsePartFileCreate", err.Error())
@@ -100,7 +75,7 @@ func (s *Response) Send(writer any) rest.IErrorArgs {
 	} else {
 		// Если нет файлов, просто отправляем JSON
 		w.Header().Set("Content-Type", "application/json")
-		if err := json.NewEncoder(w).Encode(s.data); err != nil {
+		if err := json.NewEncoder(w).Encode(s.RData()); err != nil {
 			return rest.ErrorMessage("ErrResponseDataJsonEncode", err.Error())
 		}
 	}
@@ -110,17 +85,14 @@ func (s *Response) Send(writer any) rest.IErrorArgs {
 
 // Успрешный ответ
 
-func ResponseSuccess(data mjson.Map, files map[string]rest.IReadCloserLen) *Response {
-	return &Response{
-		code:  200,
-		data:  data,
-		files: files,
+func ResponseSuccess(req rest.IRequest) rest.IRequestOut {
+	return &RequestOut{
+		IRequest: req,
 	}
 }
 
-func ResponseError(code int, err rest.IErrorArgs) *Response {
-	return &Response{
-		code: code,
-		err:  err,
+func ResponseError(err rest.IErrorArgs) rest.IRequestOut {
+	return &RequestOut{
+		Err: err,
 	}
 }

+ 24 - 17
rest_http/rest.go

@@ -46,15 +46,19 @@ func responseError(w http.ResponseWriter, err rest.IErrorArgs, code int) {
 
 // handle
 func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
-	// Инициализация restRequest
-	rr := &Request{
-		Request:        r,
-		core:           s.core,
-		data:           mjson.Map{},
-		files:          make(map[string]rest.IReadCloserLen),
-		tokenGenerator: s.server.TokenGenerate,
+	// Инициализация rest Request
+	rr := &RequestIn{
+		RequestIn: &rest.RequestIn{
+			IRequest: &rest.Request{
+				Type:    rest.RequestTypeIn,
+				Command: r.URL.Path,
+				Data:    mjson.Map{},
+				Files:   make(rest.RequestFiles),
+			},
+			GeneratorToken: s.server.TokenGenerate,
+			Core:           s.core,
+		},
 	}
-
 	// Парсим Bearer токен и извлекаем claims
 	authHeader := r.Header.Get("Authorization")
 	if authHeader != "" {
@@ -91,13 +95,15 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 
 		data, check := multiPartForm.Value["data"]
 		if check {
-			err := json.NewDecoder(bytes.NewBuffer([]byte(data[0]))).Decode(&rr.data)
+			rData := rr.RData()
+			err := json.NewDecoder(bytes.NewBuffer([]byte(data[0]))).Decode(&rData)
 			if err != nil {
 				responseError(w, rest.ErrorMessage("ErrMultipartDataParse", err.Error()), 500)
 				return
 			}
 		}
 
+		files := rr.RFiles()
 		for filename, headers := range multiPartForm.File {
 			for _, header := range headers {
 				file, err := header.Open()
@@ -115,12 +121,13 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 					file,
 					header.Size,
 				)
-				rr.files[filename] = rlFile
+				files[filename] = rlFile
 			}
 		}
-		defer rr.Close()
+		defer rr.RClose()
 	} else {
-		err := json.NewDecoder(r.Body).Decode(&rr.data)
+		data := rr.RData()
+		err := json.NewDecoder(r.Body).Decode(&data)
 		if err != nil {
 			responseError(w, rest.ErrorMessage("ErrDataParse", err.Error()), 500)
 			return
@@ -136,9 +143,9 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	log.Println(rr.data)
+	//log.Println(rr.RData)
 	// serialize
-	if err := rest.Serialize(rr.data, command); err != nil {
+	if err := rest.Serialize(rr.RData(), command); err != nil {
 		responseError(w, err, 500)
 		return
 	}
@@ -147,7 +154,7 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 	if validator, check := command.(rest.IValidator); check {
 		resp := validator.Validate(rr)
 		if resp != nil {
-			if err := resp.Send(w); err != nil {
+			if err := resp.Write(w); err != nil {
 				responseError(w, err, 500)
 			}
 			return
@@ -156,8 +163,8 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 
 	// execute
 	resp := command.Execute(rr)
-	if err := resp.Send(w); err != nil {
+	if err := resp.Write(w); err != nil {
 		responseError(w, err, 500)
 	}
-	resp.Close()
+	resp.RClose()
 }

+ 15 - 0
rest_websocket/app_config.go

@@ -0,0 +1,15 @@
+package rest_websocket
+
+import (
+	"context"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
+	"git.ali33.ru/fcg-xvii/rest"
+)
+
+type appConfig struct {
+	app            rest.IApplicationStream
+	core           any
+	ctx            context.Context
+	tokenGenerator func(json.Map, int64) (string, error)
+}

+ 15 - 19
rest_websocket/application.go

@@ -2,38 +2,31 @@ package rest_websocket
 
 import (
 	"context"
-
-	"git.ali33.ru/fcg-xvii/rest"
+	"log"
 )
 
-type IApplication interface {
-	rest.IApplication
-	Incoming() chan<- *WebSocket
-	Disconnect() chan<- *WebSocket
-}
-
 func NewApplication(ctx context.Context) *Application {
 	app := &Application{
 		ctx:          ctx,
-		chIncoming:   make(chan *WebSocket),
-		chDisconnect: make(chan *WebSocket),
+		chConnect:    make(chan *SocketServer),
+		chDisconnect: make(chan *SocketServer),
 	}
 	go app.work()
 	return app
 }
 
 type Application struct {
-	sockets      []*WebSocket
+	sockets      []*SocketServer
 	ctx          context.Context
-	chIncoming   chan *WebSocket
-	chDisconnect chan *WebSocket
+	chConnect    chan *SocketServer
+	chDisconnect chan *SocketServer
 }
 
-func (s *Application) Incoming() chan<- *WebSocket {
-	return s.chIncoming
+func (s *Application) Connect() chan<- *SocketServer {
+	return s.chConnect
 }
 
-func (s *Application) Disconnect() chan<- *WebSocket {
+func (s *Application) Disconnect() chan<- *SocketServer {
 	return s.chDisconnect
 }
 
@@ -42,12 +35,14 @@ func (s *Application) work() {
 		select {
 		case <-s.ctx.Done():
 			return
-		case socket, ok := <-s.chIncoming:
+		case socket, ok := <-s.chConnect:
 			if !ok {
 				return
 			}
+			log.Println("CH-CONNECT")
 			s.addSocket(socket)
 		case socket := <-s.chDisconnect:
+			log.Println("CH-DISCONNECT")
 			for i, sock := range s.sockets {
 				if sock == socket {
 					s.sockets[i] = nil
@@ -67,11 +62,12 @@ func (s *Application) searchFree() (int, bool) {
 	return -1, false
 }
 
-func (s *Application) addSocket(socket *WebSocket) {
+func (s *Application) addSocket(socket *SocketServer) {
+	log.Println("ADD_SOCKET", socket)
 	// ищем свободный слот
 	index, free := s.searchFree()
 	if !free {
-		sockets := make([]*WebSocket, len(s.sockets)+50)
+		sockets := make([]*SocketServer, len(s.sockets)+50)
 		copy(sockets, s.sockets)
 		sockets[len(s.sockets)] = socket
 	} else {

+ 3 - 62
rest_websocket/client.go

@@ -1,70 +1,11 @@
 package rest_websocket
 
-import (
-	"io"
-	"log"
-	"time"
+import "github.com/gorilla/websocket"
 
-	"github.com/gorilla/websocket"
-)
-
-func NewClient(addr string) (*Client, error) {
+func NewClient(addr string) (*Socket, error) {
 	conn, _, err := websocket.DefaultDialer.Dial(addr, nil)
 	if err != nil {
 		return nil, err
 	}
-	cl := &Client{
-		conn: conn,
-	}
-	go cl.work()
-	return cl, nil
-}
-
-type Client struct {
-	conn             *websocket.Conn
-	messagesIncoming map[int64]*Message
-}
-
-func (s *Client) SendMessage(mes *Message) (err error) {
-	var writer io.WriteCloser
-	if writer, err = s.conn.NextWriter(websocket.BinaryMessage); err != nil {
-		return
-	}
-	err = mes.Write(writer)
-	writer.Close()
-	return
-}
-
-func (s *Client) work() {
-	for {
-		// Read message from server
-		mType, r, err := s.conn.NextReader()
-		if err != nil {
-			log.Println(err)
-			return
-		}
-		switch mType {
-		case websocket.TextMessage, websocket.BinaryMessage:
-			// Обработка текстового сообщения
-			mes, err := ReadMessage(r)
-			if err != nil {
-				log.Println("data error: ", err)
-				return
-			}
-			log.Println("RESPONSE", mes)
-		case websocket.PingMessage:
-			// Отправка Pong в ответ на Ping
-			//s.sendLocker.Lock()
-			err := s.conn.WriteControl(websocket.PongMessage, nil, time.Now().Add(time.Second))
-			//s.sendLocker.Unlock()
-			if err != nil {
-				log.Println("pong write:", err)
-				return
-			}
-		case websocket.CloseMessage:
-			// Обработка закрытия соединения
-			log.Println("websocket connection closed")
-			return
-		}
-	}
+	return NewSocket(conn), nil
 }

+ 0 - 242
rest_websocket/message.go

@@ -1,242 +0,0 @@
-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()
-	}
-}

+ 0 - 35
rest_websocket/message_answer.go

@@ -1,35 +0,0 @@
-package rest_websocket
-
-import (
-	"git.ali33.ru/fcg-xvii/rest"
-)
-
-type Answer struct {
-	*Message
-	err    rest.IErrorArgs
-	socket *WebSocket
-}
-
-func (s *Answer) IsError() bool {
-	return s.err != nil
-}
-
-func (s *Answer) KeySet(key string, val any) {
-	s.data[key] = val
-}
-
-func (s *Answer) FileSet(name string, file rest.IReadCloserLen) {
-	if !s.IsError() {
-		s.files[name] = file
-	}
-}
-
-func (s *Answer) Send(any) rest.IErrorArgs {
-	if s.IsError() {
-		s.data = s.err.Map()
-		s.data["error"] = true
-		s.Close()
-		s.files = nil
-	}
-	return s.socket.sendMessage(s.Message)
-}

+ 0 - 22
rest_websocket/message_event.go

@@ -1,22 +0,0 @@
-package rest_websocket
-
-import (
-	"git.ali33.ru/fcg-xvii/go-tools/json"
-	"git.ali33.ru/fcg-xvii/rest"
-)
-
-type MessageEvent struct {
-	*MessageEvent
-}
-
-func (s *MessageEvent) Type() rest.RequestType {
-	return rest.RequestTypeEvent
-}
-
-func (s *MessageEvent) ResponseSuccess(data json.Map, files map[string]rest.IReadCloserLen) rest.IResponse {
-	return nil
-}
-
-func (s *MessageEvent) ResponseError(code int, err rest.IErrorArgs) rest.IResponse {
-	return nil
-}

+ 0 - 38
rest_websocket/message_incoming.go

@@ -1,38 +0,0 @@
-package rest_websocket
-
-import (
-	"git.ali33.ru/fcg-xvii/go-tools/json"
-	"git.ali33.ru/fcg-xvii/rest"
-)
-
-type MessageIncoming struct {
-	*Message
-	socket *WebSocket
-}
-
-func (s *MessageIncoming) Type() rest.RequestType {
-	return rest.RequestTypeMessage
-}
-
-func (s *MessageIncoming) Answer(data json.Map, files map[string]rest.IReadCloserLen) rest.IResponse {
-	return &Answer{
-		Message: &Message{
-			id:    s.id,
-			mType: rest.RequestTypeAnswer,
-			data:  data,
-			files: files,
-		},
-		socket: s.socket,
-	}
-}
-
-func (s *MessageIncoming) AnswerError(code int, err rest.IErrorArgs) rest.IResponse {
-	return &Answer{
-		Message: &Message{
-			id:    s.id,
-			mType: rest.RequestTypeAnswer,
-		},
-		err:    err,
-		socket: s.socket,
-	}
-}

+ 35 - 72
rest_websocket/request.go

@@ -1,101 +1,64 @@
 package rest_websocket
 
 import (
-	"log"
-
 	"git.ali33.ru/fcg-xvii/go-tools/json"
 	"git.ali33.ru/fcg-xvii/rest"
 )
 
-/*
-type IRequest interface {
-	Type() RequestType
-	IsAuth() bool
-	Command() string
-	Auth() json.Map
-	Data() json.Map
-	FileKeys() []string
-	File(name string) (IReadCloserLen, bool)
-	GenerateToken(data json.Map, expire int64) (string, error)
-	Root() any
-	Core() any
-	ResponseSuccess(data json.Map, files map[string]IReadCloserLen) IResponse
-	ResponseError(code int, err IErrorArgs) IResponse
-	Close()
-}
-)*/
-
-type Request struct {
-	mes *Message
-}
-
-func (s *Request) Type() rest.RequestType {
-	return s.mes.mType
-}
-
-func (s *Request) IsAuth() bool {
-	return s.mes.owner.Auth != nil
+type RequestIn struct {
+	*rest.RequestStream
+	owner *SocketServer
+	core  any
 }
 
-func (s *Request) Auth() json.Map {
-	return s.mes.owner.Auth
+func (s *RequestIn) Auth() json.Map {
+	return s.owner.auth
 }
 
-func (s *Request) SetAuth(m json.Map) {
-	s.mes.owner.Auth = m
+func (s *RequestIn) SetAuth(auth json.Map) {
+	s.owner.auth = auth
 }
 
-func (s *Request) Command() string {
-	return s.mes.command
+func (s *RequestIn) GenerateToken(data json.Map, expire int64) (string, error) {
+	return s.owner.appConf.tokenGenerator(data, expire)
 }
 
-func (s *Request) Data() json.Map {
-	return s.mes.data
+func (s *RequestIn) ROwner() rest.IOwner {
+	return s.owner
 }
 
-func (s *Request) FileKeys() []string {
-	return s.mes.FileKeys()
+func (s *RequestIn) RCore() any {
+	return s.core
 }
 
-func (s *Request) File(name string) (rest.IReadCloserLen, bool) {
-	res, check := s.mes.files[name]
+func (s *RequestIn) ClientData(key string) (any, bool) {
+	res, check := s.owner.clientData[key]
 	return res, check
 }
 
-func (s *Request) GenerateToken(data json.Map, expire int64) (string, error) {
-	return s.mes.owner.appConf.tokenGenerator(data, expire)
+func (s *RequestIn) SetClientData(key string, data any) {
+	s.owner.clientData[key] = data
 }
 
-func (s *Request) Root() any {
-	return s.mes.owner
-}
-
-func (s *Request) Core() any {
-	return s.mes.owner.appConf.core
-}
-
-func (s *Request) ResponseSuccess(data json.Map, files map[string]rest.IReadCloserLen) rest.IResponse {
-	if s.mes.mType == rest.RequestTypeEvent {
-		return &rest.ResponseEmpty{}
-	}
-	return &Response{
-		mes:   s.mes,
-		data:  data,
-		files: files,
+func (s *RequestIn) OutSuccess(data json.Map, files rest.RequestFiles) rest.IRequestOut {
+	return &rest.RequestStream{
+		ID: s.ID,
+		Request: &rest.Request{
+			Type:    rest.RequestTypeOut,
+			Command: s.Command,
+			Data:    data,
+			Files:   files,
+		},
 	}
 }
 
-func (s *Request) ResponseError(code int, err rest.IErrorArgs) rest.IResponse {
-	log.Println("RESP-ERROR", err.Map())
-	if s.mes.mType == rest.RequestTypeEvent {
-		return &rest.ResponseEmpty{}
+func (s *RequestIn) OutError(err rest.IErrorArgs) rest.IRequestOut {
+	return &rest.RequestStream{
+		ID: s.ID,
+		Request: &rest.Request{
+			Type:    rest.RequestTypeOut,
+			Command: s.Command,
+			Data:    err.Map(),
+		},
 	}
-	return &Response{
-		mes: s.mes,
-		err: err,
-	}
-}
-
-func (s *Request) Close() {
-	s.mes.Close()
 }

+ 44 - 0
rest_websocket/request_wait.go

@@ -0,0 +1,44 @@
+package rest_websocket
+
+import (
+	"log"
+	"time"
+
+	"git.ali33.ru/fcg-xvii/rest"
+)
+
+func newWaitRequest(id int64, timeout time.Time) *waitRequest {
+	now := time.Now()
+	if timeout.Before(now) {
+		timeout = now.Add(time.Second * 10)
+	}
+	res := &waitRequest{
+		id:       id,
+		answer:   make(chan *rest.RequestStream, 1),
+		answerIn: make(chan *rest.RequestStream, 1),
+		timeout:  timeout,
+	}
+	go res.exec()
+	return res
+}
+
+type waitRequest struct {
+	id       int64
+	answer   chan *rest.RequestStream
+	answerIn chan *rest.RequestStream
+	timeout  time.Time
+}
+
+func (s *waitRequest) exec() {
+	select {
+	case answ, ok := <-s.answerIn:
+		if !ok {
+			close(s.answer)
+			return
+		}
+		s.answer <- answ
+	case <-time.After(s.timeout.Sub(time.Now())):
+		log.Println("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR")
+		close(s.answer)
+	}
+}

+ 0 - 66
rest_websocket/response.go

@@ -1,66 +0,0 @@
-package rest_websocket
-
-import (
-	"log"
-	"time"
-
-	"git.ali33.ru/fcg-xvii/go-tools/json"
-	"git.ali33.ru/fcg-xvii/rest"
-)
-
-/*
-type IResponse interface {
-	IsError() bool
-	KeySet(key string, val any)
-	FileSet(name string, file IReadCloserLen)
-	Close()
-	Send(writer any) IErrorArgs
-}
-*/
-
-type Response struct {
-	mes   *Message
-	err   rest.IErrorArgs
-	data  json.Map
-	files map[string]rest.IReadCloserLen
-}
-
-func (s *Response) IsError() bool {
-	return s.err != nil
-}
-
-func (s *Response) KeySet(key string, val any) {
-	s.data[key] = val
-}
-
-func (s *Response) FileSet(name string, file rest.IReadCloserLen) {
-	s.files[name] = file
-}
-
-func (s *Response) Close() {
-	for _, file := range s.files {
-		file.Close()
-	}
-	s.files = nil
-}
-
-func (s *Response) Send(writer any) rest.IErrorArgs {
-	if s.IsError() {
-		log.Println("ERRRRRRRRRRRRRRRRRRR")
-		s.Close()
-		s.data = s.err.Map()
-		s.data["error"] = true
-	}
-	mes := &Message{
-		id:      s.mes.id,
-		command: s.mes.command,
-		mType:   rest.RequestTypeAnswer,
-		timeout: time.Now(),
-		data:    s.data,
-		files:   s.files,
-	}
-	log.Println("AAA", mes)
-	err := s.mes.owner.sendMessage(mes)
-	mes.Close()
-	return err
-}

+ 6 - 13
rest_websocket/rest.go

@@ -1,23 +1,14 @@
 package rest_websocket
 
 import (
-	"context"
 	"log"
 	"net/http"
 
-	"git.ali33.ru/fcg-xvii/go-tools/json"
 	"git.ali33.ru/fcg-xvii/rest"
 	"github.com/gorilla/websocket"
 )
 
-type appConfig struct {
-	app            IApplication
-	core           any
-	ctx            context.Context
-	tokenGenerator func(json.Map, int64) (string, error)
-}
-
-func New(app IApplication, core any) *Rest {
+func New(app rest.IApplicationStream, core any) *Rest {
 	return &Rest{
 		upgrader: &websocket.Upgrader{
 			ReadBufferSize:  1024,
@@ -36,7 +27,6 @@ type Rest struct {
 	server   rest.IServer
 }
 
-// Listen start server in other goroutine
 func (s *Rest) Prepare(server rest.IServer, httpPrefix string) {
 	s.appConf.ctx = server.Context()
 	s.appConf.tokenGenerator = server.TokenGenerate
@@ -50,6 +40,9 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 	if err != nil {
 		log.Println(err)
 	}
-	socket := newWebSocket(ws, s.appConf)
-	s.appConf.app.Incoming() <- socket
+	socket := NewSocketServer(
+		NewSocket(ws),
+		s.appConf,
+	)
+	s.appConf.app.Connect() <- socket
 }

+ 177 - 0
rest_websocket/socket.go

@@ -0,0 +1,177 @@
+package rest_websocket
+
+import (
+	"context"
+	"errors"
+	"io"
+	"log"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"git.ali33.ru/fcg-xvii/rest"
+	"github.com/gorilla/websocket"
+)
+
+func NewSocket(conn *websocket.Conn) *Socket {
+	ctx, cancel := context.WithCancel(context.Background())
+	ws := &Socket{
+		conn:        conn,
+		wait:        new(sync.Map),
+		ctx:         ctx,
+		cancel:      cancel,
+		writeLocker: &sync.Mutex{},
+		chIn:        make(chan *rest.RequestStream, 10),
+	}
+	ws.lastWrite.Store(time.Now().Unix())
+	go ws.read()
+	return ws
+}
+
+type Socket struct {
+	ctx         context.Context
+	cancel      context.CancelFunc
+	conn        *websocket.Conn
+	wait        *sync.Map
+	chIn        chan *rest.RequestStream
+	writeLocker *sync.Mutex
+	idCounter   atomic.Int64
+	lastWrite   atomic.Int64
+	pingEnable  bool
+}
+
+func (s *Socket) Context() context.Context {
+	return s.ctx
+}
+
+// MessagesIn возвращает канал, в который будут переданы все входящие сообщения (rest.RequestTypeMessage и rest.RequestTypeEvent)
+func (s *Socket) MessagesIn() <-chan *rest.RequestStream {
+	return s.chIn
+}
+
+// getID увеличивает счётчик сообщений на единицу и возвраащает результат. используется для маркирования идентификаторами исходящих сообщений
+func (s *Socket) getID() int64 {
+	return s.idCounter.Add(1)
+}
+
+// read реализует чтение входящих сообщений
+func (s *Socket) read() {
+	//defer log.Println("work close...")
+	// контекст
+	s.ctx, s.cancel = context.WithCancel(context.Background())
+	// создаем канал для обработки входящих сообщений
+	chIn := s.exec()
+	for {
+		// Read message from server
+		mType, r, err := s.conn.NextReader()
+		if err != nil {
+			s.cancel()
+			log.Println(err)
+			return
+		}
+		switch mType {
+		case websocket.TextMessage, websocket.BinaryMessage:
+			// Обработка текстового или бинарного сообщения
+			req, err := rest.ReadRequestStream(r)
+			if err != nil {
+				log.Println("data error: ", err)
+				return
+			}
+			log.Println("RESPONSE", req)
+			chIn <- req
+		}
+	}
+}
+
+// exec реализует обработку сообщений.
+func (s *Socket) exec() chan<- *rest.RequestStream {
+	ch := make(chan *rest.RequestStream)
+	go func() {
+		//defer log.Println("exec close...")
+		for {
+			select {
+			// закрытие контекста
+			case <-s.ctx.Done():
+				return
+			// новое сообщение
+			case req, ok := <-ch:
+				if !ok {
+					return
+				}
+				//log.Println("OOOOOOOOOOOOOOOOOOOOOOOOOOO")
+				switch req.Type {
+				case rest.RequestTypeIn:
+					s.chIn <- req
+				case rest.RequestTypeEvent:
+					s.chIn <- req
+				case rest.RequestTypeOut:
+					log.Println("answer in", req.ID)
+					ir, check := s.wait.Load(req.ID)
+					if check {
+						rreq := ir.(*waitRequest)
+						s.wait.Delete(rreq.id)
+						rreq.answerIn <- req
+					}
+				}
+			// чистка просроченных сообщений и отправка пинга (при необходимости)
+			case <-time.After(time.Second * 10):
+				// чистим сообщения без ответа по дедлайну
+				now := time.Now()
+				s.wait.Range(func(key, val any) bool {
+					if val.(*waitRequest).timeout.Before(now) {
+						log.Println("CLEAN...", key)
+						s.wait.Delete(key)
+					}
+					return true
+				})
+				// отправляем пинг для проверки, живое соединение или нет
+				if s.pingEnable && now.Unix()-s.lastWrite.Load() > 10 {
+					log.Println("PING")
+					//s.writeLocker.Lock()
+					err := s.conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(time.Second))
+					//s.writeLocker.Unlock()
+					if err != nil {
+						log.Println("ping send error")
+						s.conn.Close()
+						return
+					}
+					s.lastWrite.Store(time.Now().Unix())
+				}
+			}
+		}
+	}()
+	return ch
+}
+
+func (s *Socket) nextWriter(messageType int) (io.WriteCloser, error) {
+	s.writeLocker.Lock()
+	res, err := s.conn.NextWriter(messageType)
+	s.writeLocker.Unlock()
+	s.lastWrite.Store(time.Now().Unix())
+	return res, err
+}
+
+func (s *Socket) SendMessage(req *rest.RequestStream) (ch <-chan *rest.RequestStream, err error) {
+	switch req.Type {
+	case rest.RequestTypeIn:
+		req.ID = s.getID()
+		clReq := newWaitRequest(req.ID, req.Timeout)
+		ch = clReq.answer
+		s.wait.Store(req.ID, clReq)
+	case rest.RequestTypeEvent, rest.RequestTypeOut:
+	default:
+		return nil, errors.New("unexpected request type")
+	}
+	var writer io.WriteCloser
+	if writer, err = s.nextWriter(websocket.BinaryMessage); err != nil {
+		return
+	}
+	err = req.Write(writer)
+	writer.Close()
+	return
+}
+
+func (s *Socket) Close() {
+	s.cancel()
+	s.conn.Close()
+}

+ 210 - 0
rest_websocket/socket.goo

@@ -0,0 +1,210 @@
+package rest_websocket
+
+import (
+	"context"
+	"io"
+	"log"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
+	"git.ali33.ru/fcg-xvii/rest"
+	"github.com/gorilla/websocket"
+)
+
+func newSocket(conn *websocket.Conn, appConf *appConfig) *Socket {
+	ctx, cancel := context.WithCancel(context.Background())
+	ws := &Socket{
+		conn:              conn,
+		waitMessages:      new(sync.Map),
+		ctx:               ctx,
+		cancel:            cancel,
+		sendLocker:        &sync.Mutex{},
+		appConf:           appConf,
+		chMessageIncoming: make(chan *Request, 10),
+	}
+	go ws.read()
+	return ws
+}
+
+type Socket struct {
+	conn              *websocket.Conn
+	ctx               context.Context
+	cancel            context.CancelFunc
+	sendLocker        *sync.Mutex
+	appConf           *appConfig
+	chMessageIncoming chan *Request
+	auth              json.Map
+	clientData        json.Map
+	waitMessages      *sync.Map
+	idCounter         atomic.Int64
+}
+
+func (s *Socket) IsSocket() bool {
+	return true
+}
+
+func (s *Socket) Context() context.Context {
+	return s.ctx
+}
+
+func (s *Socket) MessagesIncoming() <-chan *Request {
+	return s.chMessageIncoming
+}
+
+func (s *Socket) Close() {
+	s.cancel()
+	s.conn.Close()
+}
+
+func (s *Socket) ID() int64 {
+	return s.idCounter.Add(1)
+}
+
+func (s *Socket) NextWriter(messageType int) (w io.WriteCloser, err rest.IErrorArgs) {
+	s.sendLocker.Lock()
+	var wErr error
+	w, wErr = s.conn.NextWriter(websocket.TextMessage)
+	if wErr != nil {
+		err = rest.ErrorMessage("ErrWriterInit", err.Error())
+	}
+	s.sendLocker.Unlock()
+	return
+}
+
+func (s *Socket) SendMessage(msg rest.IRequestOut) rest.IErrorArgs {
+	if msg.RType() == rest.RequestTypeIn {
+		id := s.ID()
+		if req, check := msg.(*Request); check {
+			req.id = id
+		} else {
+			msg = &Request{
+				id:      id,
+				Timeout: time.Now().Add(time.Second * 5),
+				Request: &rest.Request{
+					Type:    rest.RequestTypeIn,
+					Command: msg.RCommand(),
+					Data:    msg.RData(),
+					Files:   msg.RFiles(),
+				},
+			}
+		}
+		s.waitMessages.Store(id, msg)
+	}
+	w, err := s.NextWriter(websocket.BinaryMessage)
+	if err != nil {
+		return err
+	}
+	err = msg.Write(w)
+	w.Close()
+	return err
+}
+
+func (s *Socket) execMessage(reqIn *RequestIn) {
+	defer reqIn.RClose()
+	log.Println("Message", reqIn)
+	var reqOut rest.IRequestOut
+	command, check := s.appConf.app.Executer(reqIn)
+	if !check {
+		reqOut = reqIn.OutError(rest.ErrorMessage("ErrNotFound", "command is not found"))
+	} else {
+		// serialize
+		if err := rest.Serialize(reqIn.RData(), command); err != nil {
+			log.Println("serialize error", err)
+			return
+		}
+		// validate
+		if validator, check := command.(rest.IValidator); check {
+			reqOut = validator.Validate(reqIn)
+			if reqOut != nil {
+
+				if err := s.SendMessage(reqOut); err != nil {
+					log.Println("socket send error", err.Map())
+				}
+				return
+			}
+		}
+		reqOut = command.Execute(reqIn)
+	}
+	log.Println("RESP", reqOut)
+	s.SendMessage(reqOut)
+	reqOut.RClose()
+}
+
+func (s *Socket) messageIncoming() chan<- *Request {
+	chIncoming := make(chan *Request, 100)
+	tClean := time.NewTicker(time.Second * 60)
+	defer tClean.Stop()
+	go func() {
+		for {
+			select {
+			case <-s.ctx.Done():
+				return
+			case req, ok := <-chIncoming:
+				if !ok {
+					return
+				}
+				log.Println("INCOMING!!!!!")
+				switch req.Type {
+				case rest.RequestTypeIn, rest.RequestTypeEvent:
+					reqIn := &RequestIn{
+						Request: req,
+						owner:   s,
+						core:    s.appConf.core,
+					}
+					s.execMessage(reqIn)
+				case rest.RequestTypeOut:
+					log.Println("ANSWER")
+				}
+			case <-tClean.C:
+				now := time.Now()
+				s.waitMessages.Range(func(key, value any) bool {
+					if value.(*Request).Timeout.Before(now) {
+						s.waitMessages.Delete(key)
+					}
+					return true
+				})
+			}
+		}
+	}()
+	return chIncoming
+}
+
+func (s *Socket) read() {
+	chIncoming := s.messageIncoming()
+	defer s.cancel()
+	for {
+		// Read message from server
+		mType, r, err := s.conn.NextReader()
+		if err != nil {
+			log.Println(err)
+			return
+		}
+		log.Println("MTYPE...", mType)
+		switch mType {
+		case websocket.TextMessage, websocket.BinaryMessage:
+			// Обработка текстового сообщения
+			mes, err := ReadRequest(r)
+			if err != nil {
+				log.Println("data error: ", err)
+				return
+			}
+			chIncoming <- mes
+		case websocket.PingMessage:
+			// Отправка Pong в ответ на Ping
+			log.Println("PING......")
+			s.sendLocker.Lock()
+			err := s.conn.WriteControl(websocket.PongMessage, nil, time.Now().Add(time.Second))
+			s.sendLocker.Unlock()
+			if err != nil {
+				log.Println("pong write:", err)
+				return
+			}
+		case websocket.CloseMessage:
+			// Обработка закрытия соединения
+			log.Println("websocket connection closed")
+			return
+		}
+	}
+}

+ 123 - 0
rest_websocket/socket_server.go

@@ -0,0 +1,123 @@
+package rest_websocket
+
+import (
+	"context"
+	"log"
+	"time"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
+	"git.ali33.ru/fcg-xvii/rest"
+)
+
+func NewSocketServer(socket *Socket, appConf *appConfig) *SocketServer {
+	res := &SocketServer{
+		socket:     socket,
+		appConf:    appConf,
+		auth:       json.Map{},
+		chIn:       make(chan rest.IRequestIn, 1),
+		clientData: make(map[string]any),
+	}
+	go res.work()
+	return res
+}
+
+type SocketServer struct {
+	socket     *Socket
+	appConf    *appConfig
+	auth       json.Map
+	chIn       chan rest.IRequestIn
+	clientData json.Map
+}
+
+func (s *SocketServer) ClientData(key string) (any, bool) {
+	res, check := s.clientData[key]
+	return res, check
+}
+
+func (s *SocketServer) SetClientData(key string, data any) {
+	s.clientData[key] = data
+}
+
+func (s *SocketServer) Auth() json.Map {
+	return s.auth
+}
+
+func (s *SocketServer) SetAuth(auth json.Map) {
+	s.auth = auth
+}
+
+func (s *SocketServer) In() <-chan rest.IRequestIn {
+	return s.chIn
+}
+
+func (s *SocketServer) IsStream() bool {
+	return true
+}
+
+func (s *SocketServer) Context() context.Context {
+	return s.socket.Context()
+}
+
+func (s *SocketServer) Close() {
+	s.socket.Close()
+}
+
+func (s *SocketServer) SendMessage(req rest.IRequestOut) (<-chan *rest.RequestStream, error) {
+	var request *rest.RequestStream
+	if rreq, check := req.(*rest.RequestStream); check {
+		request = rreq
+	} else {
+		request = &rest.RequestStream{
+			Request: &rest.Request{
+				Type:    req.RType(),
+				Command: req.RCommand(),
+				Data:    req.RData(),
+				Files:   req.RFiles(),
+			},
+			Timeout: time.Now().Add(time.Second * 10),
+		}
+	}
+	return s.socket.SendMessage(request)
+}
+
+func (s *SocketServer) work() {
+	for {
+		select {
+		case <-s.socket.Context().Done():
+			return
+		case req := <-s.socket.MessagesIn():
+			reqIn := &RequestIn{
+				RequestStream: req,
+				owner:         s,
+				core:          s.appConf.core,
+			}
+			log.Println("reqIN", reqIn)
+			var reqOut rest.IRequestOut
+			command, check := s.appConf.app.Executer(reqIn)
+			if !check {
+				reqOut = reqIn.OutError(rest.ErrorMessage("ErrNotFound", "command is not found"))
+			} else {
+				// serialize
+				if err := rest.Serialize(reqIn.RData(), command); err != nil {
+					log.Println("serialize error", err)
+					return
+				}
+				// validate
+				if validator, check := command.(rest.IValidator); check {
+					reqOut = validator.Validate(reqIn)
+					if reqOut != nil {
+						if _, err := s.SendMessage(reqOut); err != nil {
+							log.Println("socket send error", err)
+						}
+						return
+					}
+				}
+				reqOut = command.Execute(reqIn)
+			}
+			log.Println("RESPPPPP", reqOut)
+			s.SendMessage(reqOut)
+			reqOut.RClose()
+			//s.chIn <- reqIn
+		}
+	}
+}

+ 124 - 0
rest_websocket/tools.go

@@ -0,0 +1,124 @@
+package rest_websocket
+
+import (
+	"io"
+
+	"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 ReadBuf(r io.Reader, size int, field string) ([]byte, rest.IErrorArgs) {
+	buf := make([]byte, size)
+	if _, err := r.Read(buf); err != nil {
+		return nil, ioError(field, err)
+	}
+	return buf, nil
+}
+
+func ReadBufSize(r io.Reader, lenSize int, field string) ([]byte, rest.IErrorArgs) {
+	var size int64
+	if err := ReadInt64(r, lenSize, field+"_size", &size); err != nil {
+		return nil, err
+	}
+	buf, err := ReadBuf(r, int(size), field)
+	return buf, err
+}
+
+func ReadInt64(r io.Reader, size int, field string, result *int64) rest.IErrorArgs {
+	buf := make([]byte, size)
+	if _, err := r.Read(buf); err != nil {
+		return ioError(field, err)
+	}
+	*result = BytesToInt64(buf)
+	return nil
+}
+
+func ReadString(r io.Reader, lenSize int, field string, result *string) rest.IErrorArgs {
+	strBuf, err := ReadBufSize(r, lenSize, field)
+	if err != nil {
+		return err
+	}
+	*result = string(strBuf)
+	return nil
+}
+
+func ReadByte(r io.Reader, field string, result *byte) rest.IErrorArgs {
+	buf, err := ReadBuf(r, 1, field)
+	if err != nil {
+		return err
+	}
+	*result = buf[0]
+	return nil
+}
+
+// запись
+
+func WriteBuf(w io.Writer, buf []byte, field string) rest.IErrorArgs {
+	if _, err := w.Write(buf); err != nil {
+		return ioError(field, err)
+	}
+	return nil
+}
+
+func WriteInt64(w io.Writer, val int64, size int, field string) rest.IErrorArgs {
+	buf := Int64ToBytes(val, size)
+	return WriteBuf(w, buf, field)
+}
+
+func WriteString(w io.Writer, val, field string, lenSize int) rest.IErrorArgs {
+	// длина
+	if err := WriteInt64(w, int64(len(val)), lenSize, field+"_size"); err != nil {
+		return err
+	}
+	// строка
+	if len(val) == 0 {
+		return nil
+	}
+	return WriteBuf(w, []byte(val), field)
+}
+
+func WriteByte(w io.Writer, val byte, field string) rest.IErrorArgs {
+	return WriteBuf(w, []byte{val}, field)
+}
+
+func WriteBufSize(w io.Writer, val []byte, lenSize int, field string) rest.IErrorArgs {
+	// длина
+	if err := WriteInt64(w, int64(len(val)), lenSize, field+"_size"); err != nil {
+		return err
+	}
+	// буфер
+	return WriteBuf(w, val, field)
+}

+ 0 - 162
rest_websocket/websocket.go

@@ -1,162 +0,0 @@
-package rest_websocket
-
-import (
-	"context"
-	"log"
-	"sync"
-	"time"
-
-	"git.ali33.ru/fcg-xvii/go-tools/json"
-	"git.ali33.ru/fcg-xvii/rest"
-	"github.com/gorilla/websocket"
-)
-
-func newWebSocket(conn *websocket.Conn, appConf *appConfig) *WebSocket {
-	ctx, cancel := context.WithCancel(context.Background())
-	ws := &WebSocket{
-		conn:              conn,
-		waitMessages:      make(map[int64]*MessageIncoming),
-		ctx:               ctx,
-		cancel:            cancel,
-		sendLocker:        &sync.Mutex{},
-		appConf:           appConf,
-		chMessageIncoming: make(chan *MessageIncoming, 10),
-	}
-	go ws.read()
-	return ws
-}
-
-type WebSocket struct {
-	conn              *websocket.Conn
-	waitMessages      map[int64]*MessageIncoming
-	ctx               context.Context
-	cancel            context.CancelFunc
-	sendLocker        *sync.Mutex
-	appConf           *appConfig
-	chMessageIncoming chan *MessageIncoming
-	Auth              json.Map
-	Data              json.Map
-}
-
-func (s *WebSocket) MessagesIncoming() <-chan *MessageIncoming {
-	return s.chMessageIncoming
-}
-
-func (s *WebSocket) Close() {
-
-}
-
-func (s *WebSocket) sendMessage(msg *Message) rest.IErrorArgs {
-	s.sendLocker.Lock()
-	writer, err := s.conn.NextWriter(websocket.TextMessage)
-	s.sendLocker.Unlock()
-	if err != nil {
-		return rest.ErrorMessage("ErrWriterInit", err.Error())
-	}
-	err = msg.Write(writer)
-	writer.Close()
-	return nil
-}
-
-func (s *WebSocket) execMessage(mes *Message) {
-	defer mes.Close()
-	log.Println("Message", mes)
-	req := &Request{
-		mes: mes,
-	}
-	var resp rest.IResponse
-	command, check := s.appConf.app.Executer(req)
-	if !check {
-		resp = req.ResponseError(404, rest.ErrorMessage("ErrNotFound", "command is not found"))
-	} else {
-		// serialize
-		if err := rest.Serialize(mes.data, command); err != nil {
-			log.Println("serialize error", err)
-			return
-		}
-		// validate
-		if validator, check := command.(rest.IValidator); check {
-			resp := validator.Validate(req)
-			if resp != nil {
-				if err := resp.Send(nil); err != nil {
-					log.Println("socket send error", err.Map())
-				}
-				return
-			}
-		}
-		resp = command.Execute(req)
-	}
-	log.Println("RESP", resp)
-	resp.Send(nil)
-	resp.Close()
-}
-
-func (s *WebSocket) messageIncoming() chan<- *Message {
-	chIncoming := make(chan *Message, 100)
-	tClean := time.NewTicker(time.Second * 60)
-	defer tClean.Stop()
-	go func() {
-		for {
-			select {
-			case <-s.ctx.Done():
-				return
-			case mes, ok := <-chIncoming:
-				if !ok {
-					return
-				}
-				mes.owner = s
-				log.Println("INCOMING!!!!!")
-				switch mes.mType {
-				case rest.RequestTypeMessage, rest.RequestTypeEvent:
-					s.execMessage(mes)
-				case rest.RequestTypeAnswer:
-					log.Println("ANSWER")
-				}
-			case <-tClean.C:
-				now := time.Now()
-				for id, mes := range s.waitMessages {
-					if mes.timeout.Before(now) {
-						delete(s.waitMessages, id)
-					}
-				}
-			}
-		}
-	}()
-	return chIncoming
-}
-
-func (s *WebSocket) read() {
-	chIncoming := s.messageIncoming()
-	defer s.cancel()
-	for {
-		// Read message from server
-		mType, r, err := s.conn.NextReader()
-		if err != nil {
-			log.Println(err)
-			return
-		}
-		switch mType {
-		case websocket.TextMessage, websocket.BinaryMessage:
-			// Обработка текстового сообщения
-			mes, err := ReadMessage(r)
-			if err != nil {
-				log.Println("data error: ", err)
-				return
-			}
-			chIncoming <- mes
-		case websocket.PingMessage:
-			// Отправка Pong в ответ на Ping
-			s.sendLocker.Lock()
-			err := s.conn.WriteControl(websocket.PongMessage, nil, time.Now().Add(time.Second))
-			s.sendLocker.Unlock()
-			if err != nil {
-				log.Println("pong write:", err)
-				return
-			}
-		case websocket.CloseMessage:
-			// Обработка закрытия соединения
-			log.Println("websocket connection closed")
-			return
-		}
-	}
-}

+ 36 - 0
rest_websocket/z_test.go

@@ -10,6 +10,41 @@ import (
 	"git.ali33.ru/fcg-xvii/rest"
 )
 
+func TestRequest(t *testing.T) {
+	rbuf := make([]byte, 100)
+	fTmp := rest.NewReadCloserLen(
+		io.NopCloser(bytes.NewReader(rbuf)),
+		int64(len(rbuf)),
+	)
+	req := &rest.RequestStream{
+		ID:      10,
+		Timeout: time.Now().Add(time.Minute),
+		Request: &rest.Request{
+			Type:    rest.RequestTypeOut,
+			Command: "f-command",
+			Data: json.Map{
+				"one": 1,
+				"two": 2,
+			},
+			Files: rest.RequestFiles{
+				"file.txt": fTmp,
+			},
+		},
+	}
+
+	var buf bytes.Buffer
+	req.Write(&buf)
+	t.Log(buf.Bytes())
+
+	rreq, err := rest.ReadRequestStream(&buf)
+	if err != nil {
+		t.Log(err.Map().JSONPrettyString())
+	}
+	t.Log(rreq)
+	t.Log(rreq.Files)
+}
+
+/*
 func TestMessage(t *testing.T) {
 	rbuf := make([]byte, 100)
 	fTmp := rest.NewReadCloserLen(
@@ -43,3 +78,4 @@ func TestMessage(t *testing.T) {
 
 	mes2.Close()
 }
+*/

+ 123 - 0
tools.go

@@ -0,0 +1,123 @@
+package rest
+
+import (
+	"io"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
+)
+
+// конверторы
+
+// 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) IErrorArgs {
+	return NewError(
+		"ErrIO",
+		json.Map{
+			"field": field,
+			"error": err.Error(),
+		},
+	)
+}
+
+// чтение
+
+func ReadBuf(r io.Reader, size int, field string) ([]byte, IErrorArgs) {
+	buf := make([]byte, size)
+	if _, err := r.Read(buf); err != nil {
+		return nil, ioError(field, err)
+	}
+	return buf, nil
+}
+
+func ReadBufSize(r io.Reader, lenSize int, field string) ([]byte, IErrorArgs) {
+	var size int64
+	if err := ReadInt64(r, lenSize, field+"_size", &size); err != nil {
+		return nil, err
+	}
+	buf, err := ReadBuf(r, int(size), field)
+	return buf, err
+}
+
+func ReadInt64(r io.Reader, size int, field string, result *int64) IErrorArgs {
+	buf := make([]byte, size)
+	if _, err := r.Read(buf); err != nil {
+		return ioError(field, err)
+	}
+	*result = BytesToInt64(buf)
+	return nil
+}
+
+func ReadString(r io.Reader, lenSize int, field string, result *string) IErrorArgs {
+	strBuf, err := ReadBufSize(r, lenSize, field)
+	if err != nil {
+		return err
+	}
+	*result = string(strBuf)
+	return nil
+}
+
+func ReadByte(r io.Reader, field string, result *byte) IErrorArgs {
+	buf, err := ReadBuf(r, 1, field)
+	if err != nil {
+		return err
+	}
+	*result = buf[0]
+	return nil
+}
+
+// запись
+
+func WriteBuf(w io.Writer, buf []byte, field string) IErrorArgs {
+	if _, err := w.Write(buf); err != nil {
+		return ioError(field, err)
+	}
+	return nil
+}
+
+func WriteInt64(w io.Writer, val int64, size int, field string) IErrorArgs {
+	buf := Int64ToBytes(val, size)
+	return WriteBuf(w, buf, field)
+}
+
+func WriteString(w io.Writer, val, field string, lenSize int) IErrorArgs {
+	// длина
+	if err := WriteInt64(w, int64(len(val)), lenSize, field+"_size"); err != nil {
+		return err
+	}
+	// строка
+	if len(val) == 0 {
+		return nil
+	}
+	return WriteBuf(w, []byte(val), field)
+}
+
+func WriteByte(w io.Writer, val byte, field string) IErrorArgs {
+	return WriteBuf(w, []byte{val}, field)
+}
+
+func WriteBufSize(w io.Writer, val []byte, lenSize int, field string) IErrorArgs {
+	// длина
+	if err := WriteInt64(w, int64(len(val)), lenSize, field+"_size"); err != nil {
+		return err
+	}
+	// буфер
+	return WriteBuf(w, val, field)
+}

Some files were not shown because too many files changed in this diff