Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapter for express js middleware #47

Open
Risto-Stevcev opened this issue Sep 5, 2017 · 3 comments
Open

Adapter for express js middleware #47

Risto-Stevcev opened this issue Sep 5, 2017 · 3 comments

Comments

@Risto-Stevcev
Copy link

Being able to reuse existing express middleware would be really nice in hyper. As it currently is, nodejs apps using express that are transitioning to purescript wouldn't be able to switch to hyper immediately because all of their infrastructure would be bound to the express middleware. They would be forced to either use the express wrapper (not ideal) or leave it as is (even worse).

I think hyper middleware should be completely compatible with express middleware as far as I can tell. What needs to be done to get this adapter going?

@owickstrom
Copy link
Collaborator

owickstrom commented Sep 6, 2017

This is indeed an interesting proposal, and it has been on my TODO list before. 🙂 Not sure why there's no issue or note of it anywhere.

As long as they're using the Hyper.Node.Server, then yes, everything could be compatible. Then it is up to the one doing the FFI binding to provide a proper type signature, and perhaps to provide a value in the components of the Conn. Such a value can be parameterized to safely track state, if needed.

The tricky part, I guess, is that many Express middleware might perform a side effect, or not, and then just hand over to the next, which also might perform side effects. Indeed, this is why I started with Hyper in the first place. 😄 But I think Express middleware should be "lifted" to Hyper on an individual basis, not with a general liftMiddleware that "works" on any Express middleware, as their type signatures and effects probably will differ a lot.

One other thing, which I wanted to write about but I haven't had time, is how error handling differs with Hyper from Express (as we're tracking effects). In Express, errors are passed in continuations to signal that the response hasn't been sent and that it's up to someone else to do it. In Hyper, you need to "wrap" fallback middleware with other middleware that might fail. For example, the fileServer middleware takes an on404 middleware as a fallback. This affects how Express middleware should be integrated as well. Maybe a generic function for doing FFI, that takes an Express middleware and returns a Hyper middleware with a similar type to fileServer, could work.

To sum up; I think this is valuable step for Hyper to take, and that it could, as you say, enable more projects to gradually migrate from Express. That is probably the selling point. I think Haskellers are unlikely to jump on the NodeJS runtime just to track side effects in middleware with Hyper, but battle-scarred NodeJS developers might long for some type safety in their backend projects.

@Risto-Stevcev
Copy link
Author

Yeah that's true. For the express middleware I've used before, it modifies the request or response object and add a new property to it, and it also performs a side effect like reading/writing to a database (like for a store for passportjs)
I might try to write some kind of adapter, I'm transitioning an old nodejs express app and I really want to use hyper for it instead of express. But I'm a ways away from getting to that part of the code

@srghma
Copy link

srghma commented Apr 17, 2020

here is an example

module Server where

import Prelude
import Effect
import Effect.Aff
import Effect.Aff.Class (liftAff, class MonadAff)
import Data.Tuple (Tuple(Tuple))

import Control.Monad.Indexed.Qualified as IndexedMonad
import Hyper.Node.FileServer           as Hyper
import Hyper.Node.Server               as Hyper
import Hyper.Response                  as Hyper
import Hyper.Request                   as Hyper
import Hyper.Status                    as Hyper
import Hyper.Middleware                as Hyper
import Hyper.Conn                      as Hyper
import Node.Encoding                   as NodeBuffer
import Node.Buffer                     as NodeBuffer
import Data.Function.Uncurried         as Functions
import Effect.Uncurried                as Effect.Uncurried
import Node.Path                       as Path
import Node.HTTP                       as NodeHttp
import Data.Newtype                    as Newtype
import Debug.Trace                     as Debug

type ForeignMiddleware = Functions.Fn3 NodeHttp.Request NodeHttp.Response (Effect Unit) (Effect Unit)

mkMiddlewareFromForeign
  :: forall c
  .  ForeignMiddleware
  ->  Hyper.Middleware
     Aff
     (Hyper.Conn Hyper.HttpRequest (Hyper.HttpResponse Hyper.StatusLineOpen) c)
     (Hyper.Conn Hyper.HttpRequest (Hyper.HttpResponse Hyper.ResponseEnded) c)
     Unit
  -> Hyper.Middleware
     Aff
     (Hyper.Conn Hyper.HttpRequest (Hyper.HttpResponse Hyper.StatusLineOpen) c)
     (Hyper.Conn Hyper.HttpRequest (Hyper.HttpResponse Hyper.ResponseEnded) c)
     Unit
mkMiddlewareFromForeign foreignMiddleware (Hyper.Middleware app) = Hyper.Middleware $ \conn ->
   makeAff \cb -> do
      let (Hyper.HttpRequest (nodeHttpRequest :: NodeHttp.Request) _requestData) = conn.request
          (Hyper.HttpResponse nodeHttpResponse) = conn.response
          onNext = do
             Debug.traceM "traceM: before calling cb"
             runAff_ cb (app conn)
             Debug.traceM "traceM: after calling cb"
      Debug.traceM "traceM: before calling foreignMiddleware"
      Functions.runFn3 foreignMiddleware nodeHttpRequest nodeHttpResponse onNext
      Debug.traceM "traceM: after calling foreignMiddleware"
      pure nonCanceler


foreign import _testMiddleware :: ForeignMiddleware

testMiddleware
  :: forall c
  .  Hyper.Middleware
     Aff
     (Hyper.Conn Hyper.HttpRequest (Hyper.HttpResponse Hyper.StatusLineOpen) c)
     (Hyper.Conn Hyper.HttpRequest (Hyper.HttpResponse Hyper.ResponseEnded) c)
     Unit
  -> Hyper.Middleware
     Aff
     (Hyper.Conn Hyper.HttpRequest (Hyper.HttpResponse Hyper.StatusLineOpen) c)
     (Hyper.Conn Hyper.HttpRequest (Hyper.HttpResponse Hyper.ResponseEnded) c)
     Unit
testMiddleware = mkMiddlewareFromForeign _testMiddleware

main :: Effect Unit
main =
  let
    app = IndexedMonad.do
      Debug.traceM "traceM: app is called"
      Hyper.writeStatus Hyper.statusOK
      Hyper.headers []
      Hyper.respond "<h1>Hello from app!</h1>"

    app' = app # testMiddleware
  in Hyper.runServer Hyper.defaultOptionsWithLogging {} app'
const testMiddlewareEndsResponse = function(req, res, nxt) {
  console.log('testMiddlewareEndsResponse')
  console.log('Outside')
  console.log('Request Type:', req.method)

  return function() {
    console.log('Inside')
    console.log('Request Type:', req.method)
    console.log('Ending reponse')

    res.writeHead(200);
    res.end('<h1>Hello from testMiddlewareEndsResponse!</h1>')
  }
}

const testMiddlewareCallsNext = function(req, res, next) {
  console.log('testMiddlewareCallsNext')
  console.log('Outside')
  console.log('Request Type:', req.method)

  return function() {
    console.log('Inside')
    console.log('Request Type:', req.method)
    console.log('calling next')

    next()
  }
}

exports._testMiddleware = testMiddlewareCallsNext

examle1 output

Listening on http://0.0.0.0:3000
'traceM: before calling foreignMiddleware'
testMiddlewareEndsResponse
Outside
Request Type: GET
Inside
Request Type: GET
Ending reponse
'traceM: after calling foreignMiddleware'

examle2 output

Listening on http://0.0.0.0:3000
'traceM: before calling foreignMiddleware'
testMiddlewareCallsNext
Outside
Request Type: GET
Inside
Request Type: GET
calling next
'traceM: before calling cb'
'traceM: app is called'
'traceM: after calling cb'
'traceM: after calling foreignMiddleware'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants