eXept Software AG Logo

Smalltalk/X Webserver

Documentation of class 'WebSocketStream':

Home

Documentation
www.exept.de
Everywhere
for:
[back]

Class: WebSocketStream


Inheritance:

   Object
   |
   +--Stream
      |
      +--WebSocketStream

Package:
stx:goodies/communication
Category:
Net-Communication-Websocket
Version:
rev: 1.134 date: 2022/12/05 08:49:17
user: cg
file: WebSocketStream.st directory: goodies/communication
module: stx stc-classLibrary: communication

Description:


I handle the WebSocket protocol as defined in:
    https://datatracker.ietf.org/doc/html/rfc6455

use #next to wait for new data.
use #nextPut: to send data.
use #nextPutAll: to send an array of data.
use #ping to get the ping time duration or nil if timeout (30s).
use #close to shutdown and cleanup everything.

to get familiar play with the tests in /stx/goodies/regression/WebSocketTest.st

avoids hge allocations by using files see #minBytesForUsingFile
ATTENTION: you may receive a Filename when the data is too big.
ATTENTION: when the other side sends a small file, you may receive the data as ByteArray instead of file.

data to be sent must be a String, ByteArray or Filename
you receive a String when a String was sent (as long as the data is not too big -> file). 
you receive a ByteArray when a ByteArray was sent (as long as the data is not too big -> file).

copyright

COPYRIGHT (c) 2020 by eXept Software AG All Rights Reserved This software is furnished under a license and may be used only in accordance with the terms of that license and with the inclusion of the above copyright notice. This software may not be provided or otherwise made available to, or used by, any other person. No title to or ownership of the software is hereby transferred.

Class protocol:

accessing
o  forceCompressionBitForTestingPurpose
this is just used for testing purpose
due to we only support decompression in #receive,
we need to force this bit in #send to be able to test the decompression

self forceCompressionBitForTestingPurpose
self forceCompressionBitForTestingPurpose:true
self forceCompressionBitForTestingPurpose:false

o  forceCompressionBitForTestingPurpose: aValue
this is just used for testing purpose
due to we only support decompression in #receive,
we need to force this bit in #send to be able to test the decompression

self forceCompressionBitForTestingPurpose
self forceCompressionBitForTestingPurpose:true
self forceCompressionBitForTestingPurpose:false

o  maxBytesPerFrame
keep this number small,
to be able to put control frames between

o  maxBytesPerFrame: aNumber
keep this number small,
to be able to put control frames between

constants
o  kOpCodeClose

o  kOpCodePing

o  kOpCodePong

o  kOpCodeText

o  maxPingTimeDuration

o  minBytesForUsingFile
if incoming data exceeds this #min value,
use a file to store the incoming data (instead of using memory)

o  readerWriterProcessPriority

debugging
o  debug
self debug:true.
self verbose:true.
self verboseSpeed:true.
self verboseProtocol:true.

self debug:false.
self verbose:false.
self verboseSpeed:false.
self verboseProtocol:false.

o  debug: aBoolean
self debug:true.
self verbose:true.
self verboseSpeed:true.
self verboseProtocol:true.

self debug:false.
self verbose:false.
self verboseSpeed:false.
self verboseProtocol:false.

o  verbose
self debug:true.
self verbose:true.
self verboseSpeed:true.
self verboseProtocol:true.

self debug:false.
self verbose:false.
self verboseSpeed:false.
self verboseProtocol:false.

o  verbose: aBoolean
self debug:true.
self verbose:true.
self verboseSpeed:true.
self verboseProtocol:true.

self debug:false.
self verbose:false.
self verboseSpeed:false.
self verboseProtocol:false.

o  verboseProtocol
self debug:true.
self verbose:true.
self verboseSpeed:true.
self verboseProtocol:true.

self debug:false.
self verbose:false.
self verboseSpeed:false.
self verboseProtocol:false.

o  verboseProtocol: aBoolean
self debug:true.
self verbose:true.
self verboseSpeed:true.
self verboseProtocol:true.

self debug:false.
self verbose:false.
self verboseSpeed:false.
self verboseProtocol:false.

o  verboseSpeed

self debug:true.
self verbose:true.
self verboseSpeed:true.
self verboseProtocol:true.

self debug:false.
self verbose:false.
self verboseSpeed:false.
self verboseProtocol:false.

o  verboseSpeed: aBoolean

self debug:true.
self verbose:true.
self verboseSpeed:true.
self verboseProtocol:true.

self debug:false.
self verbose:false.
self verboseSpeed:false.
self verboseProtocol:false.

initialization
o  initialize
Debug := true.

Usage example(s):

WebSocketConstants initializeConstants.

instance creation
o  asStreamForClientOnSocket: aSocket

o  asStreamForServerOnSocket: aSocket

o  new
use #asStreamForClientOnSocket: or #asStreamForServerOnSocket:

o  newInstance


Instance protocol:

accessing
o  closedByErrorCallback: aOneOrZeroArgBlock
Modified (format): / 07-10-2020 / 18:17:09 / exept MBP

o  closedByPeerCallback: aOneOrZeroArgBlock
Modified (format): / 07-10-2020 / 18:17:05 / exept MBP

o  connectionTargetName

o  connectionTargetName: aString
Modified (format): / 12-03-2022 / 13:05:05 / cg

o  getPeer

accessing data
o  next
blocking: return the next complete message
the contents of all data frames (until frame with final flag) concatenated

o  nextOrNil
non-blocking: return the next complete message if there is one; otherwise nil.
the contents of all data frames (until frame with final flag) concatenated

o  nextPut: textOrByteArrayOrFilename
send a message
i.e. the whole data you want to send
not framed, not encoded, just plain text, byte array or filename
the real send will encode the frame for this data

o  nextPutAll: arrayOfTextOrByteArrayOrFilename
send many messages but ensures their order,
so the messages will income with the same order

o  nextWithTimeout: secondsOrTimeDurationOrNil
blocking: return the next complete message or nil if a non-nil timeout is happening.
Returns the contents of all data frames (until frame with final flag) concatenated

actions
o  ping

o  setUsedOnServerSide
is this stream currently used on the client or server side?
the frame encoding and decoding differs for client and server side

o  updateProcessNames

change & update
o  notify: event with: payload orElse: aBlock
tell listeners
or if there are no listeners,
evaluate aBlock (which usually puts it into the receive queue)

o  on: event do: aBlock
register an event listener

converting
o  decodeFrame: byteArrayOrReindexedCollectionWithByteArray
returns the #decodeData or nil for error and the caller should close this websocket.
See: https://datatracker.ietf.org/doc/html/rfc6455
(Renamed from #decodeFrameHybi17:
HYBI was the name of an IETF Working Group and the 17th report resulted in RFC6455.
so this name was a historic leftover and has be changed).

o  encodeFrame: nilOrStringOrByteArrayOrReindexedCollectionWithByteArrayArg isFinal: isFinal opCode: opCode
Renamed from #encodeFrameHybi17....
HYBI was the name of an IETF Working Group and the 17th report resulted in RFC6455.
so this name was a historic leftover and has be changed.

o  encodeFrameText: aString

o  maskOrUnmaskPayload: stringOrByteArrayOrReindexedCollectionWithByteArray withMask: maskArg
returns a new masked byte array

o  maskOrUnmaskPayload: stringOrByteArrayOrReindexedCollectionWithByteArray withMask: maskArg into: outBufferOrNil
returns either outBufferOrNil filled with the data
or a new masked byte array;
The incoming buffer can be overwritten iff it is known to contain temporary data
(i.e. not provided by the websocket-using-client, but allocated here.
Especially, if the buffer contains data from a file.
This can avoid an extra allocation of possibly big buffers.

Usage example(s):

        SPEED UP stx code:
            payload doWithIndex:[:eachByte :index |
                payload
                    at:index
                    put:(eachByte bitXor:(mask at:((index - 1) \\ 4) + 1)).
            ].

        TESTS:
            (self basicNew 
                maskOrUnmaskPayload:#[0 0 0 0] 
                withMask:#[1 2 3 4]) 
                    asByteArray

frames
o  closeFrame

o  pingFrame

o  pongFrame

helper
o  criticalSocketNextPutAll: data
if the current process is not the #writerProcess
increase the process priority over the #writerProcess priority
to give control frames (ping, close etc.) or explicit write calls priority

o  criticalSocketNextPutAll: data ignoreIsConnectionClosed: ignoreIsConnectionClosed
if the current process is not the #writerProcess
increase the process priority over the #writerProcess priority
to give control frames (ping, close etc.) or explicit write calls priority

o  decompressMessageContents: contentsOrContentsAsFile
try to handle compression in an inconform way,
because I (the server) did not negotiated any compression extension.
we still try to handle this data, because the python debug interface
sends data with this bit set, even without negotiated before 24.04.2020
this is just a try, we dont know yet if the python debug interface will run now

o  handleErrorForProcessLabeled: aLabel do: aBlock
assume connection is corrupted,
don't send close frame to a corrupted connection

initialization & release
o  close
when you close it by yourself you dont need a callback

o  closeDueToErrorInPing
we already have a write error, the next write will not work either

o  closeWithSendCloseFrame: doSendCloseFrame errorMessage: errorMessage callback: aCallback
https://expeccoalm.exept.de/D480706

Usage example(s):

#closeWithSendCloseFrame: already called or 
     this is a recursive call (triggered by terminating the read/write processes)

o  initialize
lazy initialization from instance method

o  socket: aSocket

o  startProcesses
ensure same as writer process... otherwise may one process is blocking the other

o  startProcessing
the WebSocket has been accepted,
start operating as web socket now.

before it was just a web socket object, to be passed to for e.g. #acceptsWebSocket:
please do not change the socket with #setNonBlocking or start the processes by #startProcesses
before it was realy clear, that this web socket will be accepted and before the client's accepted response has been sent

logging
o  log: message
(comment from inherited method)
same as showCR:.
Added to allow for Transcript.log(...) to be used in a similar way as console.log(...).
(and, by the way, JS-actions in expecco see a binding for console -> Transcript.
Not for non-JavaScript usage

o  log: message data: reindexedCollection

o  log: message data: reindexedCollection showData: showData

o  logFacility
(comment from inherited method)
the 'log facility';
this is used by the Logger both as a prefix to the log message,
and maybe (later) used to filter and/or control per-facility log thresholds.
The default here is to base the facility on my class

printing
o  printOn: aStream
(comment from inherited method)
append a user printed representation of the receiver to aStream.
The format is suitable for a human - not meant to be read back.

The default here is to output the receiver's class name.
BUT: this method is heavily redefined for objects which
can print prettier.

processes
o  readerProcessLoop
reads all incoming packets and puts them into the receiveQueue or notifies any listener

o  writerProcessLoop
shuffles all packets from the sendQueue to the socket

queries
o  hasEmptyReceiveQueue

o  isConnectionClosed

o  isUsedOnServerSide
is this stream currently used on the client or server side?
the frame encoding and decoding differs for client and server side

o  lastDataReceivedOrInstanceCreationTimestamp


Private classes:

    ConnectionClosedError
    Frame
    Message


ST/X 7.7.0.0; WebServer 1.702 at 20f6060372b9.unknown:8081; Sat, 07 Sep 2024 23:36:07 GMT