kazu-yamamoto / mighttpd2

File/CGI web server on Warp
BSD 3-Clause "New" or "Revised" License
137 stars 20 forks source link

WebSockets over Reverse Proxy #13

Open konn opened 9 years ago

konn commented 9 years ago

I use Mighttpd2's reverse proxy functionality to assign subdomains to web services.

I'm currently developing service using WebSockets, but it seems that mighty doesn't support websockets connection over the reverse proxy.

Here is minimal example:

[ping.example.com]  
/ >> 127.0.0.1:3939/
{-# LANGUAGE ExtendedDefaultRules, MultiParamTypeClasses, NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings, QuasiQuotes, TemplateHaskell, TypeFamilies  #-}
module Main where
import ClassyPrelude
import Network.Wai.Handler.Warp (run)
import Yesod
import Yesod.WebSockets

default (Text)

data WSPlayground = WSPlayground

mkYesod "WSPlayground" [parseRoutes|
  / RootR GET
|]

instance Yesod WSPlayground where
  defaultLayout wid = do
    pc <- widgetToPageContent wid
    withUrlRenderer [hamlet|
        \<!doctype html>
        <html lang=ja>
          <head>
            <meta charset="utf-8">
            <title>#{pageTitle pc}
            ^{pageHead pc}

          <body>
            ^{pageBody pc}
    |]

getRootR :: Handler Html
getRootR = do
  webSockets $ do
    "ping" <- receiveData
    sendTextData "pong"
  defaultLayout $ do
    setTitle "WSPlayground"
    addScriptRemote "//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"
    toWidget [julius|
      function log(msg) { $('<span>').text(msg).append('<br />').appendTo($("#log")); };
      $(document).ready(function(){
        $('#btn').click(function(){
          log('pinging...');
          var ws = new WebSocket("ws://ping.example.com/");
          ws.onclose = function(evt) { log('connection closed.'); };
          ws.onopen = function(opEvt) {
            ws.send('ping');
          };
          ws.onmessage = function(msgEvt) {
            log(msgEvt.data);
          };
        });
      });
    |]
    [whamlet|
      <h1>WebSockets simple test
      <button #btn>Ping
      <p #log>
    |]

main :: IO ()
main = run 3939 =<< toWaiApp WSPlayground

Under this setting, when accessing to "ping.example.com" and push "ping" button, the "ping" service raises 500 ISE:

127.0.0.1 - - [04/Aug/2015:02:28:47 +0900] "GET / HTTP/1.1" 500 - "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/600.7.12 (KHTML, like Gecko) Version/8.0.7 Safari/600.7.12"

One rather dirty workaround is to specify the port directly; but this doesn't work if session-based access-control is used. So I really need to use WebSockets thru reverse proxy.

kazu-yamamoto commented 9 years ago

Reading http://www.yesodweb.com/blog/2014/03/wai-yesod-websockets

kazu-yamamoto commented 9 years ago

Logic to borrow is here: https://github.com/fpco/http-reverse-proxy/blob/master/Network/HTTP/ReverseProxy.hs#L280

kazu-yamamoto commented 9 years ago

https://github.com/kazu-yamamoto/wai-app-file-cgi/commit/e98cab0b785d79904da55a6f31464cb79cc0a0d0 is the first try to implement WS reverse proxy.

This works well with your application. But I think more hacks are necessary to give flexibility for paths.