Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
/wsPublic

Tiny WebSocket library for Go.

License

NotificationsYou must be signed in to change notification settings

gobwas/ws

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GoDocCI

RFC6455 WebSocket implementation in Go.

Features

  • Zero-copy upgrade
  • No intermediate allocations during I/O
  • Low-level API which allows to build your own logic of packet handling andbuffers reuse
  • High-level wrappers and helpers around API inwsutil package, which allowto start fast without digging the protocol internals

Documentation

GoDoc.

Why

Existing WebSocket implementations do not allow users to reuse I/O buffersbetween connections in clear way. This library aims to export efficientlow-level interface for working with the protocol without forcing only one wayit could be used.

By the way, if you want get the higher-level tools, you can usewsutilpackage.

Status

Library is tagged asv1* so its API must not be broken during someimprovements or refactoring.

This implementation of RFC6455 passesAutobahn TestSuite and currently hasabout 78% coverage.

Examples

Example applications usingws are developed in separate repositoryws-examples.

Usage

The higher-level example of WebSocket echo server:

package mainimport ("net/http""github.com/gobwas/ws""github.com/gobwas/ws/wsutil")funcmain() {http.ListenAndServe(":8080",http.HandlerFunc(func(w http.ResponseWriter,r*http.Request) {conn,_,_,err:=ws.UpgradeHTTP(r,w)iferr!=nil {// handle error}gofunc() {deferconn.Close()for {msg,op,err:=wsutil.ReadClientData(conn)iferr!=nil {// handle error}err=wsutil.WriteServerMessage(conn,op,msg)iferr!=nil {// handle error}}}()}))}

Lower-level, but still high-level example:

import ("net/http""io""github.com/gobwas/ws""github.com/gobwas/ws/wsutil")funcmain() {http.ListenAndServe(":8080",http.HandlerFunc(func(w http.ResponseWriter,r*http.Request) {conn,_,_,err:=ws.UpgradeHTTP(r,w)iferr!=nil {// handle error}gofunc() {deferconn.Close()var (state=ws.StateServerSidereader=wsutil.NewReader(conn,state)writer=wsutil.NewWriter(conn,state,ws.OpText))for {header,err:=reader.NextFrame()iferr!=nil {// handle error}// Reset writer to write frame with right operation code.writer.Reset(conn,state,header.OpCode)if_,err=io.Copy(writer,reader);err!=nil {// handle error}iferr=writer.Flush();err!=nil {// handle error}}}()}))}

We can apply the same pattern to read and write structured responses through a JSON encoder and decoder.:

...var (r=wsutil.NewReader(conn,ws.StateServerSide)w=wsutil.NewWriter(conn,ws.StateServerSide,ws.OpText)decoder=json.NewDecoder(r)encoder=json.NewEncoder(w))for {hdr,err=r.NextFrame()iferr!=nil {returnerr}ifhdr.OpCode==ws.OpClose {returnio.EOF}varreqRequestiferr:=decoder.Decode(&req);err!=nil {returnerr}varrespResponseiferr:=encoder.Encode(&resp);err!=nil {returnerr}iferr=w.Flush();err!=nil {returnerr}}...

The lower-level example withoutwsutil:

package mainimport ("net""io""github.com/gobwas/ws")funcmain() {ln,err:=net.Listen("tcp","localhost:8080")iferr!=nil {log.Fatal(err)}for {conn,err:=ln.Accept()iferr!=nil {// handle error}_,err=ws.Upgrade(conn)iferr!=nil {// handle error}gofunc() {deferconn.Close()for {header,err:=ws.ReadHeader(conn)iferr!=nil {// handle error}payload:=make([]byte,header.Length)_,err=io.ReadFull(conn,payload)iferr!=nil {// handle error}ifheader.Masked {ws.Cipher(payload,header.Mask,0)}// Reset the Masked flag, server frames must not be masked as// RFC6455 says.header.Masked=falseiferr:=ws.WriteHeader(conn,header);err!=nil {// handle error}if_,err:=conn.Write(payload);err!=nil {// handle error}ifheader.OpCode==ws.OpClose {return}}}()}}

Zero-copy upgrade

Zero-copy upgrade helps to avoid unnecessary allocations and copying whilehandling HTTP Upgrade request.

Processing of all non-websocket headers is made in place with use of registereduser callbacks whose arguments are only valid until callback returns.

The simple example looks like this:

package mainimport ("net""log""github.com/gobwas/ws")funcmain() {ln,err:=net.Listen("tcp","localhost:8080")iferr!=nil {log.Fatal(err)}u:= ws.Upgrader{OnHeader:func(key,value []byte) (errerror) {log.Printf("non-websocket header: %q=%q",key,value)return},}for {conn,err:=ln.Accept()iferr!=nil {// handle error}_,err=u.Upgrade(conn)iferr!=nil {// handle error}}}

Usage ofws.Upgrader here brings ability to control incoming connections ontcp level and simply not to accept them by some logic.

Zero-copy upgrade is for high-load services which have to control manyresources such as connections buffers.

The real life example could be like this:

package mainimport ("fmt""io""log""net""net/http""runtime""github.com/gobwas/httphead""github.com/gobwas/ws")funcmain() {ln,err:=net.Listen("tcp","localhost:8080")iferr!=nil {// handle error}// Prepare handshake header writer from http.Header mapping.header:=ws.HandshakeHeaderHTTP(http.Header{"X-Go-Version": []string{runtime.Version()},})u:= ws.Upgrader{OnHost:func(host []byte)error {ifstring(host)=="github.com" {returnnil}returnws.RejectConnectionError(ws.RejectionStatus(403),ws.RejectionHeader(ws.HandshakeHeaderString("X-Want-Host: github.com\r\n",)),)},OnHeader:func(key,value []byte)error {ifstring(key)!="Cookie" {returnnil}ok:=httphead.ScanCookie(value,func(key,value []byte)bool {// Check session here or do some other stuff with cookies.// Maybe copy some values for future use.returntrue})ifok {returnnil}returnws.RejectConnectionError(ws.RejectionReason("bad cookie"),ws.RejectionStatus(400),)},OnBeforeUpgrade:func() (ws.HandshakeHeader,error) {returnheader,nil},}for {conn,err:=ln.Accept()iferr!=nil {log.Fatal(err)}_,err=u.Upgrade(conn)iferr!=nil {log.Printf("upgrade error: %s",err)}}}

Compression

There is aws/wsflate package to supportPermessage-Deflate CompressionExtension.

It provides minimalistic I/O wrappers to be used in conjunction with anydeflate implementation (for example, the standard library'scompress/flate).

It is also compatible withwsutil's reader and writer by providingwsflate.MessageState type, which implementswsutil.SendExtension andwsutil.RecvExtension interfaces.

package mainimport ("bytes""log""net""github.com/gobwas/ws""github.com/gobwas/ws/wsflate")funcmain() {ln,err:=net.Listen("tcp","localhost:8080")iferr!=nil {// handle error}e:= wsflate.Extension{// We are using default parameters here since we use// wsflate.{Compress,Decompress}Frame helpers below in the code.// This assumes that we use standard compress/flate package as flate// implementation.Parameters:wsflate.DefaultParameters,}u:= ws.Upgrader{Negotiate:e.Negotiate,}for {conn,err:=ln.Accept()iferr!=nil {log.Fatal(err)}// Reset extension after previous upgrades.e.Reset()_,err=u.Upgrade(conn)iferr!=nil {log.Printf("upgrade error: %s",err)continue}if_,ok:=e.Accepted();!ok {log.Printf("didn't negotiate compression for %s",conn.RemoteAddr())conn.Close()continue}gofunc() {deferconn.Close()for {frame,err:=ws.ReadFrame(conn)iferr!=nil {// Handle error.return}frame=ws.UnmaskFrameInPlace(frame)ifwsflate.IsCompressed(frame.Header) {// Note that even after successful negotiation of// compression extension, both sides are able to send// non-compressed messages.frame,err=wsflate.DecompressFrame(frame)iferr!=nil {// Handle error.return}}// Do something with frame...ack:=ws.NewTextFrame([]byte("this is an acknowledgement"))// Compress response unconditionally.ack,err=wsflate.CompressFrame(ack)iferr!=nil {// Handle error.return}iferr=ws.WriteFrame(conn,ack);err!=nil {// Handle error.return}}}()}}

You can use compression withwsutil package this way:

// Upgrade somehow and negotiate compression to get the conn...// Initialize flate reader. We are using nil as a source io.Reader because// we will Reset() it in the message i/o loop below.fr:=wsflate.NewReader(nil,func(r io.Reader) wsflate.Decompressor {returnflate.NewReader(r)})// Initialize flate writer. We are using nil as a destination io.Writer// because we will Reset() it in the message i/o loop below.fw:=wsflate.NewWriter(nil,func(w io.Writer) wsflate.Compressor {f,_:=flate.NewWriter(w,9)returnf})// Declare compression message state variable.//// It has two goals:// - Allow users to check whether received message is compressed or not.// - Help wsutil.Reader and wsutil.Writer to set/unset appropriate//   WebSocket header bits while writing next frame to the wire (it//   implements wsutil.RecvExtension and wsutil.SendExtension).varmsg wsflate.MessageState// Initialize WebSocket reader as previously.// Please note the use of Reader.Extensions field as well as// of ws.StateExtended flag.rd:=&wsutil.Reader{Source:conn,State:ws.StateServerSide|ws.StateExtended,Extensions: []wsutil.RecvExtension{&msg, },}// Initialize WebSocket writer with ws.StateExtended flag as well.wr:=wsutil.NewWriter(conn,ws.StateServerSide|ws.StateExtended,0)// Use the message state as wsutil.SendExtension.wr.SetExtensions(&msg)for {h,err:=rd.NextFrame()iferr!=nil {// handle error.}ifh.OpCode.IsControl() {// handle control frame.}if!msg.IsCompressed() {// handle uncompressed frame (skipped for the sake of example// simplicity).}// Reset the writer to echo same op code.wr.Reset(h.OpCode)// Reset both flate reader and writer to start the new round of i/o.fr.Reset(rd)fw.Reset(wr)// Copy whole message from reader to writer decompressing it and// compressing again.if_,err:=io.Copy(fw,fr);err!=nil {// handle error.}// Flush any remaining buffers from flate writer to WebSocket writer.iferr:=fw.Close();err!=nil {// handle error.}// Flush the whole WebSocket message to the wire.iferr:=wr.Flush();err!=nil {// handle error.}}

[8]ページ先頭

©2009-2025 Movatter.jp