Akka HTTP WebSocket server not recognizing upgrade request

TLDR; Akka server checks headers to upgrade connections to WebSocket, isn’t able to find them

I am trying to set up a server that accepts WebSockets requests from the client using Akka HTTP. I am using this as a guide so most of the code is really copy-pasted:

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
import akka.http.scaladsl.Http
import scala.io.StdIn
import akka.stream.scaladsl.Sink
import scala.concurrent.Future
import akka.http.scaladsl.model.ws.UpgradeToWebSocket
import akka.stream.scaladsl.{Flow, Source}
import akka.http.scaladsl.model.ws.{ Message, TextMessage, BinaryMessage }

object WebServer extends App {
  
    implicit val system = ActorSystem("websockets-prototype")
    implicit val materializer = ActorMaterializer()
    implicit val ec = system.dispatcher
    val server = Http().bind(interface = "localhost", port = 9191)

    val greeterWebSocketService = Flow[Message]
        .mapConcat {
          // we match but don't actually consume the text message here,
          // rather we simply stream it back as the tail of the response
          // this means we might start sending the response even before the
          // end of the incoming message has been received
          case tm: TextMessage => TextMessage(Source.single("Hello ") ++ tm.textStream) :: Nil
          case bm: BinaryMessage =>
            // ignore binary messages but drain content to avoid the stream being clogged
            bm.dataStream.runWith(Sink.ignore)
            Nil
    }

    val requestHandler: HttpRequest => HttpResponse = {

        case HttpRequest(GET, Uri.Path("/ping"), _, _, _) => HttpResponse(entity = "PONG!")

        case req @ HttpRequest(GET, Uri.Path("/greeter"), _, _, _) => req.header[UpgradeToWebSocket] match {
            case Some(upgrade) => {
                println("I recognized the WebSocket")
                HttpResponse(200, entity = "YES!")
                // println(upgrade.toString());
                // upgrade.handleMessages(greeterWebSocketService)
            }

            case None => 
                println("Hit the WebSocket endpoint, but didn't recognize the headers")
                println(req.headers.toString())
                HttpResponse(400, entity = "Something didn't work!")
        }
    }

    val bind: Future[Http.ServerBinding] = server.to(Sink.foreach {
        conn => println("Accepting new connection")
        conn.handleWithSyncHandler(requestHandler)
    }).run()
}

I test the WebSocket server using the following cURL command:

curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Host: localhost:9191" -H "Origin: http://localhost:9191" http://localhost:9191/greeter

Whenever I run this I hit the 400 expected when the request to /greeter cannot find the Upgrade headers. This is the headers printed to console:

List(Timeout-Access: <function1>, Host: localhost:91, User-Agent: curl/7.68.0, Accept: */*, Connection: Upgrade, Upgrade: websocket, Origin: http://localhost:91)

According to Mozilla, this is supposed to be the correct headers info to upgrade to a WebSockets connection, but it doesn’t seem to work here.

Note that the same command (with URLs switched, of course) worked to test the endpoint on the WebSockets.org website. I don’t know why the header[UpgradeToWebSocket] returns None`.

Hi @nsadeh,

you are on the right track but your curl command is missing a couple of required headers:

## Split lines for readability
 curl -i -N -v 
        -H "Connection: Upgrade" 
        -H "Upgrade: websocket" 
        -H "Sec-WebSocket-Version: 13" 
        -H "Sec-WebSocket-Key: jfDTIhrY8lJvFEdttPj7uA=="  
        -H "Host: localhost:8080" 
        -H "Origin: http://localhost:8080" 
        http://localhost:8080/greeter

This is according to http://tools.ietf.org/html/rfc6455#section-4.2.1 (see the implementation).

Cheers,

1 Like

Great, that worked!

Would you mind explaining how you came up with the password?

I used the Echo Demo in http://websockets.org/ from a browser and inspected the traffic. Then copy/pasted the values. :slight_smile:

1 Like