Skip to content

The ask method

Gerasimos (Makis) Maropoulos edited this page Jul 15, 2019 · 6 revisions

As we've seen on a part of the Getting Started section the client (or server) can also block/wait and catch a response from the remote side anywhere in the application flow, you are not limited to the asynchronous event-driven API. Both methods and styles are used in a typical, common, application.

The Conn Ask

Use this method when you have access to the connection value that you want to ask for.

The Conn.Ask and NSConn.Ask methods do exactly that.

In this section you will learn how to create a question-answer flow and how to manage incoming remote event errors, remember? Event callback can return an error too.

At this example, for the shake of simplicity, we will set to a client the role to ask and server to reply, but this can be converted to a bidirectional flow too, each NSConn and Conn's method can be used by both server and client sides.

The application is fairly simple, the client will provide a date month-day-year and server will reply back if its a work day or if the date is not valid for work, it's a day off or the provided time format is invalid.

Create & run the Server

Let's dive in by defining our server-side.

const namespace = "default"

var errDayOff = errors.New("day off")

func runServer() {
    websocketServer := neffos.New(
        gorilla.DefaultUpgrader,
        neffos.Namespaces{
            namespace: neffos.Events{
                "workday": func(c *neffos.NSConn, msg neffos.Message) error {
                    date := string(msg.Body)
                    t, err := time.Parse("01-02-2006", date)
                    if err != nil {
                        return err
                    }

                    weekday := t.Weekday()

                    if weekday == time.Saturday || weekday == time.Sunday {
                        return errDayOff
                    }

                    responseText := fmt.Sprintf("it's %s, do your job.", weekday)
                    return neffos.Reply([]byte(responseText))
                },
            },
        })

    router := http.NewServeMux()
    router.Handle("/", websocketServer)

    log.Println("Serving websockets on localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", router))
}

Create & run the Client

Continue with our client-side.

func runClient() {
    // Make the client error-aware of the `errDayoff`
    // If that's missing the `Message.Err.Error() string` would still match it
    // but comparison of err == errDayOff wouldn't be a valid one.
    neffos.RegisterKnownError(errDayOff)

    ctx := context.Background()

    client, err := neffos.Dial(ctx,
        gorilla.DefaultDialer,
        "ws://localhost:8080",
        // Empty events because client does not need an event callback in this case.
        // The client is only uses the `Ask` method which allows the caller
        // to manage the response manually even if a local event is not registered at all.
        neffos.Namespaces{namespace: neffos.Events{}},
    )
    if err != nil {
        panic(err)
    }

    c, err := client.Connect(ctx, namespace)
    if err != nil {
        panic(err)
    }

    fmt.Println("Please specify a date of format: mm-dd-yyyy")

    for {
        fmt.Print(">> ")
        var date string
        fmt.Scanln(&date)

        response, err := c.Ask(ctx, "workday", []byte(date))
        if err != nil {
            if err == errDayOff {
                // >> 06-29-2019
                // it's a day off!
                fmt.Println("it's a day off!")
            } else {
                // >> 13-29-2019
                // error received: parsing time "13-29-2019": month out of range
                fmt.Printf("error received: %v\n", err)
            }

            continue
        }

        // >> 06-24-2019
        // it's Monday, do your job.
        fmt.Println(string(response.Body))
    }
}

Note that the error managment works the same way with the event-driven API too. Instead of response, [err] := c.Ask(...) the err error is stored at the incoming Message.Err field, a quick view:

func onSomething(c *neffos.NSConn, msg neffos.Message) error {
    if msg.Err != nil {
        if msg.Err == errDayOff {
            // [handle errDayoff...]
        }
    }
}

Read more at Errors section.

The Server Ask

Use this method when you:

  • do NOT have access to the connection value that you want to ask for or
  • want to wait for a reply from a client that may be served by other neffos server (when your app is scaled-out).

The Server.Ask method is like Server.Broadcast but it blocks until response, from a specific connection (when "msg.To" is filled) or from the first connection which will reply to this "msg".

  1. Accepts a context for deadline as its first input argument.
  2. The second argument is the request message which should be sent to a specific namespace:event like the Conn/NSConn.Ask.
Ask(ctx context.Context, msg Message) (Message, error)

Example Client Event:

"onAsk": func(c *neffos.NSConn, msg neffos.Message) error {
    return neffos.Reply([]byte("I am fine"))
}

Usage

response, err := websocketServer.Ask(context.TODO(), neffos.Message{
    // To:     toConnID,
    Namespace: namespace,
    Event:     "onAsk",
    Body:      []byte("how are you?"),
})
if err != nil {
    // [handle err...]
}

response.Body would be []byte("I am fine").