Code Monkey home page Code Monkey logo

Comments (15)

essenciary avatar essenciary commented on July 22, 2024 1

Hi @MikeInnes, thanks very much for the follow up, you're very kind as always, much appreciated.

Indeed it's obvious that Julia's core team stays true to the primary goal, of being the best tool for number crunching. But definitely, it's great to have low level libraries like HttpServer and WebSockets. And even higher level ones like Escher and Patchwork, though these too are more oriented towards data visualization.

That makes sense, given that Julia is still at v 0.4.x and there's a lot more to do. However, I strongly believe that the best approach to getting Julia to become a mainstream generic programming language (assuming that this is desired in the first place) is a (very) good web framework. That did it for ruby and that did it for elixir (heck, and even for erlang!). Fintech alone, though very attractive financially for developers (see F#), is too niche to make a language mainstream (again, see F# vs C#).

Nuff said, end rant. Felt the need to lobby for web dev a bit, maybe I can plant a seed and it will end up as a higher priority on the roadmap towards v1 :)


404, 5xx - good point, makes sense. The thing is, cause you designed it, you know how to approach things "the Mux way". But this is not at all obvious for an outsider. Now that you mentioned it, it makes total sense, indeed.


I was thinking of a mechanism for concurrently handling requests by forking Mux/HttpServer processes. So that we'd have one Mux/HttpServer process per one julia worker (per one CPU core) with each Mux/HttpServer process bound to a port. So on a 4 cores CPU we'd end up having 4 Mux/HttpServer processes, waiting for connections on 8000, 8001, 8002 and 8003. In front of this, we could have an Nginx reverse proxy / load balancer running on port 80, dispatching requests towards ports 8000 - 8003.
Makes sense?


The "communicating back" part was a different issue. I was trying to "inject" some functionality for complex routing (HTTP verbs, controller - actions, view rendering, etc) without changing the Mux source code. I did this by using functions with side effects in the Mux route matching logic.
(If this doesn't make sense, you can take a look at this, from lines 7 to 34: https://github.com/essenciary/jinnie/blob/v0.3/lib/Jinnie/src/mux_extensions.jl)
I remember it was super difficult to debug this because the code was executed in the server process and the errors were only dumped in the response, in the browser.
It would be nice for instance to have a channel where exceptions to be automatically pushed so that they'd be accessible for inspection. HttpServer has an error callback which could probably be used for this, but it does not seem to be exposed in Mux? Or maybe have a "devel" debug option, where exception bubble up into the main process?


I'm very interested in the JuliaWeb project. I've put a lot of time and effort in the last 4 months in building the basis of a julia web framework. It's still far from being production ready, but it's going great methinks. So when it comes to julia and web development, just like the pig and the bacon, I'm now beyond involved, I'm committed.

In regards to Mux, I recently realized that I needed significantly more features in the router. Besides resources and HTTP verbs, there's things like URL and path helpers (to convert controller, actions and params back into URLs) for using in views, manipulation of headers for content types and redirects, extended routing logic based on protocol, host, subdomain, etc. Thus I concluded that I could benefit from tighter integration between router and the rest of the framework and rolled out my own app server component, on top of HttpServer.

This already solved most of my issues in regards to header manipulation, exception handling and debugging, logging and forking HttpServer processes. Which makes sense - you're happy with Mux as a small agile core, while I need more features catering for a full stack framework.


Again, thanks for your help - I hope soon enough I'll be able to bring good news to the julia community in regards to web development! And I'd definitely love to see the framework as a part of JuliaWeb :)

from mux.jl.

MikeInnes avatar MikeInnes commented on July 22, 2024

So to log requests, responses, and errors, you probably want something like:

function logger(app, req)
  try
    @show req
    response = app(req)
    @show response
  catch e
    @show e
  end
end

The request goes down the stack via the req parameter, and the response goes back up it through the return value of the function. So you have access to the response at your level in the stack just by taking a look at that return value.

Let me know if that wasn't what you were look for, though.

from mux.jl.

essenciary avatar essenciary commented on July 22, 2024

Thanks for your reply - sorry for my late follow up. Let me try it and I'll get back to you.

from mux.jl.

essenciary avatar essenciary commented on July 22, 2024

@one-more-minute The logger example is nice, thanks - I was under the impression that a middleware must always return

return app(req)

in order for the request to go up the stack.

Also nice that I can access the response data - but what about the headers and the other properties? Also, how can I alter the response body and headers from within the middleware?

I have in mind something like this:

using Mux

function logger(app, req)
  @show req
  response = app(req) # this response is not the HttpServer Response - it's just a string, it does not have the :headers, :status, :data fields 
  @show response
end

function etag(app, req) # compute hash based on response headers and response body, set an etag response header and push up the stack
  println("in etag") # <-- never gets here
  response = app(req) # get the response at that point, headers and body
  response.data = response.data * "<h3>etag</h3>" # append to body 
  response.headers["ETag"] = "foo" # add some headers
  return app(req) # push up the stack
end

function rendering(app, req)
  # this should finally render the response body, setting all the headers
end

@app test = (
  Mux.defaults,
  stack(logger), 
  page("/", req -> "<h1>Hello, world!</h1>"), # this should not render, but set the response headers and body and send them up the stack
  stack(etag), # <- never gets here
  stack(rendering) # <- never gets here
)

@sync serve(test)

from mux.jl.

bauglir avatar bauglir commented on July 22, 2024

Technically @one-more-minute's example does 'return' the response from up the stack, as @show returns whatever it 'shows'. For a request to progress up the stack, you must call app(req). You can either directly return the 'response' you get from up the stack or alter it and return that down the stack.

The request goes up the chain of middlewares for as long as the middlewares keep calling the next one in line. Once this doesn't happen, the response is returned down the stack through all middlewares. So the reason your etag and rendering middlewares never get called is because the page middleware creates an 'endpoint' and if it matches the chain stops right there.

Furthermore the middleware that converts your 'response' into a Response from HttpCommon is part of the Mux.defaults stack. So if you want to be able to alter the Response you'll have to unwind that stack and include its components manually in your own stack inserting your middlewares processing the Response where appropriate.

I'm still getting to grips with Mux.jl a bit myself, so some of this information may be a bit off. But what I've described is the way I currently add headers and such. So if there are any errors in my reasoning someone else is welcome to point them out.

from mux.jl.

essenciary avatar essenciary commented on July 22, 2024

@bauglir
"So the reason your etag and rendering middlewares never get called is because the page middleware creates an 'endpoint' and if it matches the chain stops right there."
Correct, and that is my issue.

It's probably obvious by now that what I have in mind is a Rack-like architecture (in Ruby). That's a very successful design pattern and is encountered in almost all major programming languages (WSGI in Python, Rack in Ruby, Plug in Elixir, OWIN in .NET, etc).

It's an important part in growing the web ecosystem of the languages as it promotes interoperation and reusability between components (middlewares) and servers.

In this line of thought, the application itself is nothing but a (collection of) middleware(s) (a Rails app is a collection of middlewares), which allows stacking it with other middlewares and even other frameworks (mount a Sinatra app inside a Rails app, for example) and plugging in app servers at will.

In these frameworks, the middlewares communicate using minimal and simple structures. They expose a function/method that takes the request/env as a param and returns an array of 3 elements: the response code, the headers dictionary and the response body.

HttpServer works in a similar way, exposing the request and the response structures, and in my opinion it would be beneficial if Mux would expose them and would make the router just another middleware.

Interesting in regards to the Mux.defaults - I haven't looked in there, I'll check it out, thanks.

from mux.jl.

MikeInnes avatar MikeInnes commented on July 22, 2024

Mux is largely designed to follow those frameworks; it's really a superset since it has the usual middleware stacking idea but also happens to allow branching. Other frameworks I've seen only allow completely linear stacks; you can only have a single endpoint and that goes off to your router or whatever (although I should perhaps look into Rack more). I believe you'd face the same issue with them.

Anyway, the way to solve your issue is to move the etag ware so that it has higher priority than the endpoint:

@app test = (
  Mux.defaults,
  logger,
  etag,
  page("/", req -> "<h1>Hello, world!</h1>")
)

If you want you can do req -> Response(...) to generate a Response object that etag will see. Mux also has some internal functions for converting strings/dicts to responses (which are part of the default middleware), so that might be more convenient.

@bauglir It'd be cool if Mux could pull headers from a :headers key in the response dict when generating a Response object. Can help you set up a PR if you're interested in adding that feature, so that you don't have to resort to pulling things apart as much.

from mux.jl.

essenciary avatar essenciary commented on July 22, 2024

@MikeInnes Yes, you're right. The ETag middleware would have to sit higher in the stack and wait for the router/app to return, similar to the Logger middleware. My suggested workflow was flawed.

In regards to Rack, upon further reading, it seems I remembered it wrong (it's been a while since digging into it). Middlewares that act upon the response, do it in a similar way. See here the simple example code: http://www.integralist.co.uk/posts/rack-middleware.html where the next middleware is called and the response is then stored and modified.

Basically, if I understand correctly, there are 2 ways for the middlewares to handle their work:

  1. do your thing, return and push up the stack - like a request logger for example
  2. call the next middleware and wait for the response to come back. Do stuff with the response. Like your Logger example.

So this should work, I need to give it a try.

I'll dig into Mux.default to see if I can make use of that to manipulate the response headers. Looking at the notfound middleware, it looks like I can use the status() function to set the response code.

Thanks for your help.

from mux.jl.

MikeInnes avatar MikeInnes commented on July 22, 2024

No problem – and if there are things like setting headers that are inconvenient right now, I'm happy to work with people to make them easier. Good luck with your work!

from mux.jl.

essenciary avatar essenciary commented on July 22, 2024

Much appreciated, cheers!

from mux.jl.

bauglir avatar bauglir commented on July 22, 2024

@MikeInnes I'm still getting to grips a bit with what the nicest API would be for modifying headers and such. If I come up with something nice I'd be happy to send it in as a PR. I'll keep you posted.

from mux.jl.

essenciary avatar essenciary commented on July 22, 2024

@MikeInnes Back to this one after a long detour into ORM-land.
So, a very simple use case: output a JSON response. Hence, the need to set the content type to application/json.
From my perspective, the fact that such a trivial task requires digging into the codebase is a clear indication of the fact that the API does not expose all the needed features. The ability for properly outputting JSON responses should be right there with binding it to a port.
Finally, it should be noted that understanding Mux is hard. Not necessarily because of Mux itself, but because in order to understand it, one needs to dig deeper and understand HttpServer and WebSockets too.

from mux.jl.

essenciary avatar essenciary commented on July 22, 2024

So at the moment I'm a bit unsure about the real world utility as a production ready app server. I realize it's not production ready yet, of course - just wondering how hard is it to get it there.

A few other things that need changing to get there, from my perspective:

  • the funny error messages: those are nice, unless it really is 2 AM and indeed you need to get some sleep but you're struggling to make your code work :) Definitely not something for the end users or clients to see.
  • hard coded 404 responses - there should be no HTML and CSS in the Julia code. All error pages (40x, 50x) should be served from HTML files available on disk. This would allow clients to customize the pages.
  • simple parallel execution - there should be an easy way to start multiple workers to process requests in parallel. ## <- Update 1: still thinking about this. My take at this point is to have a launcher script/process that spawns multiple instances of the app/server on consecutive ports (initial_port ++). And set it all up behind an Nginx reverse proxy configured as a round robin load balancer forwarding the requests towards all the ports.
  • an easy way to communicate back to the app - for example logging debug data back into the console.

Are there any plans for continuing the project at a quicker pace? I'm not the kind of person to complain and wait for things to be done. And I'd be happy do them if anybody from the core team could work on this with me: start with a chat session to go over the code and then review and merge pull requests?

Otherwise it does seem simpler to start from scratch with my own implementation of a router on top of HttpServer and WebSockets - which would be a waste of time and energy. I would not be the only one it seems? https://github.com/codeneomatrix/Merly.jl

from mux.jl.

MikeInnes avatar MikeInnes commented on July 22, 2024

Hey @essenciary, sorry for the late reply, I'll try to respond to as much as I can here.

  • The web stack is hard – definitely agree on this, but unfortunately I think this will just be how things are until the (currently very small) user base grows. This is obviously a circular problem, and I suspect it will be true for the next couple of years – it's just not a priority for enough people. The good news is that a lot of the frameworks are there, and a bulk of the work is bug fixes and small patches for things like setting headers.
  • Hard coded error messages / 404s – these are very much intended to be placeholders, but replacing them happens in The Mux Way by adding a middleware – e.g. the logger above could be modified to catch an error and produce a custom 50x page, which would prevent the default error handler from acting. Happy to go over this in more detail.
  • I have no idea what to do about parallel execution (and I don't understand the communicating back thing – what's communicating with what? Is it workers -> master?) so proposals you have here are welcome.

Are there any plans for continuing the project at a quicker pace?

Only yours, I think. For my part, Mux does what I need so I'm not going to be spending a lot of time working on it personally. If you're willing to get your hands a little dirty (/ a lot dirty), any contributions you can make to JuliaWeb would be very welcome, and I'll do my best to make sure it's low-friction for you. Within Mux itself I'm happy to help with questions and respond to PRs, and if you're expecting a reply but not getting one then feel free to bump threads and stuff. Other packages will vary but if things aren't moving at all I'm happy to make you a maintainer (same for Mux once you're up and running). And the same goes for anyone else interested in contributing, of course.

So yeah, if you're willing to take a lead on this stuff you can make it go as fast as you want :) Let me know how I can help you get there.

from mux.jl.

jpsamaroo avatar jpsamaroo commented on July 22, 2024

Do we want to keep this issue open any longer? I think at this point it's clear that Mux probably won't grow much beyond it's simple core + utilities; and that's probably a good thing, since not everyone needs the same features that others desire.

I would personally recommend we close this issue, and then implement some of the ideas mentioned here in separate packages. The web world is broad and full of different ways to do the same things, and I think it would be best to reflect that in how we lay out the "Mux ecosystem". Small core, with extra packages to add functionality on top.

from mux.jl.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.