Skip to content

Instantly share code, notes, and snippets.

@mackee
Created December 18, 2018 01:43
Show Gist options
  • Select an option

  • Save mackee/caafebc13193638b1e11b36ff0bf479d to your computer and use it in GitHub Desktop.

Select an option

Save mackee/caafebc13193638b1e11b36ff0bf479d to your computer and use it in GitHub Desktop.

Revisions

  1. mackee created this gist Dec 18, 2018.
    62 changes: 62 additions & 0 deletions engineio.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,62 @@
    package main

    import (
    "encoding/json"
    "fmt"
    "log"
    )

    type Parser interface {
    Parse(string) error
    }

    //go:generate stringer -type=EngineIOPacketType

    type EngineIOPacketType rune

    const (
    EngineIOPacketOpen EngineIOPacketType = '0' + iota
    EngineIOPacketClose
    EngineIOPacketPing
    EngineIOPacketPong
    EngineIOPacketMessage
    EngineIOPacketUpgrade
    EngineIOPacketNoop
    )

    type EngineIO struct {
    InnerParser Parser
    }

    type EngineIOOpenMessage struct {
    PingInterval uint64 `json:"pingInterval"`
    PingTimeout uint64 `json:"pingTimeout"`
    Upgrades []string `json:"upgrades"`
    SessionID string `json:"sid"`
    }

    func (eio *EngineIO) Parse(message string) error {
    t := EngineIOPacketType(message[0])
    log.Printf("[INFO] Engine.IO type is %s", t)
    if len(message) == 1 {
    return nil
    }

    if t == EngineIOPacketOpen {
    var v EngineIOOpenMessage
    err := json.Unmarshal([]byte(message[1:]), &v)
    if err != nil {
    return fmt.Errorf("open message error: %s", err)
    }
    log.Printf("[INFO] Engine.IO open message is %+v", v)
    return nil
    }

    if eio.InnerParser != nil {
    err := eio.InnerParser.Parse(message[1:])
    if err != nil {
    return fmt.Errorf("inner parser error: %s", err)
    }
    }
    return nil
    }
    97 changes: 97 additions & 0 deletions main.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,97 @@
    package main

    import (
    "io"
    "log"
    "net/http"
    "strings"

    "github.com/gorilla/websocket"
    )

    var upgrader = websocket.Upgrader{
    ReadBufferSize: 1024,
    WriteBufferSize: 1024,
    CheckOrigin: func(r *http.Request) bool { return true },
    }

    var parser = &EngineIO{&SocketIO{}}

    func main() {
    http.HandleFunc("/socket.io/", func(w http.ResponseWriter, r *http.Request) {
    log.Println("[INFO] ===================================================")
    for key, value := range r.Header {
    values := strings.Join(value, ",")
    log.Printf("[INFO] header=%s, value=%s", key, values)
    }
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
    log.Printf("[ERROR] websocket upgrade error: %s", err)
    w.WriteHeader(http.StatusInternalServerError)
    return
    }

    ctx := r.Context()
    url := r.URL
    url.Host = "localhost:3000"
    url.Scheme = "ws"
    h := http.Header{}
    for _, header := range []string{"Cookie", "Origin"} {
    h.Add(header, r.Header.Get(header))
    }
    client, _, err := websocket.DefaultDialer.DialContext(ctx, url.String(), h)
    if err != nil {
    log.Printf("[ERROR] dial origin error: %s", err)
    w.WriteHeader(http.StatusInternalServerError)
    return
    }
    go func() {
    for {
    t, bs, err := client.ReadMessage()
    if err == io.EOF {
    break
    }
    parser.Parse(string(bs))
    switch t {
    case websocket.TextMessage:
    log.Printf("send message: %s", string(bs))
    case websocket.BinaryMessage:
    log.Printf("send binary message")
    case websocket.PingMessage:
    log.Printf("send ping: %s", string(bs))
    case websocket.PongMessage:
    log.Printf("send pong: %s", string(bs))
    }
    if err := conn.WriteMessage(t, bs); err != nil {
    log.Printf("[ERROR] fail send message: %s", err)
    }
    }
    }()

    for {
    t, bs, err := conn.ReadMessage()
    if err == io.EOF {
    break
    }
    parser.Parse(string(bs))
    switch t {
    case websocket.TextMessage:
    log.Printf("recv message: %s", string(bs))
    case websocket.BinaryMessage:
    log.Printf("recv binary message")
    case websocket.PingMessage:
    log.Printf("recv ping: %s", string(bs))
    case websocket.PongMessage:
    log.Printf("recv pong: %s", string(bs))
    }
    if err := client.WriteMessage(t, bs); err != nil {
    log.Printf("[ERROR] fail recv message: %s", err)
    }
    }
    })
    log.Println("[INFO] start server")
    err := http.ListenAndServe(":8888", nil)
    if err != nil {
    log.Printf("[ERROR] fail launch server: %s", err)
    }
    }
    60 changes: 60 additions & 0 deletions socketio.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,60 @@
    package main

    import (
    "encoding/json"
    "fmt"
    "log"
    )

    //go:generate stringer -type=SocketIOPacketType

    type SocketIOPacketType rune

    const (
    SocketIOPacketConnect SocketIOPacketType = '0' + iota
    SocketIOPacketDisconnect
    SocketIOPacketEvent
    SocketIOPacketAck
    SocketIOPacketError
    SocketIOPacketBinaryEvent
    SocketIOPacketBinaryAck
    )

    type SocketIO struct {
    }

    type SocketIOEvent struct {
    Name string
    Message json.RawMessage
    }

    func (e *SocketIOEvent) UnmarshalJSON(b []byte) error {
    var rawEvent []json.RawMessage
    err := json.Unmarshal(b, &rawEvent)
    if err != nil {
    return err
    }
    if len(rawEvent) != 2 {
    return fmt.Errorf("message is few length or too length")
    }
    e.Name = string(rawEvent[0])
    e.Message = rawEvent[1]
    return nil
    }

    func (eio *SocketIO) Parse(message string) error {
    t := SocketIOPacketType(message[0])
    log.Printf("[INFO] Socket.IO type is %s", t)
    if len(message) == 1 {
    return nil
    }
    if t == SocketIOPacketEvent {
    var event SocketIOEvent
    err := json.Unmarshal([]byte(message[1:]), &event)
    if err != nil {
    return fmt.Errorf("Socket.IO fail parse error: %s", err)
    }
    log.Printf("[INFO] Socket.IO event is name=%s, message=%s", event.Name, string(event.Message))
    }
    return nil
    }