Coder Social home page Coder Social logo

crowdhailer / raxx Goto Github PK

View Code? Open in Web Editor NEW
400.0 13.0 29.0 840 KB

Interface for HTTP webservers, frameworks and clients

Home Page: https://hexdocs.pm/raxx

License: Apache License 2.0

Elixir 99.86% HTML 0.14%
umbrella rack elixir-webservers elixir web-application-framework cowboy ace web backend framework

raxx's Introduction

Raxx

Interface for HTTP webservers, frameworks and clients.

Hex pm Build Status License

See Raxx.Kit for a project generator that helps you set up a web project based on Raxx/Ace.

Simple server

1. Defining a server

defmodule MyServer do
  use Raxx.SimpleServer

  @impl Raxx.SimpleServer
  def handle_request(%{method: :GET, path: []}, _state) do
    response(:ok)
    |> set_header("content-type", "text/plain")
    |> set_body("Hello, World!")
  end

  def handle_request(%{method: :GET, path: _}, _state) do
    response(:not_found)
    |> set_header("content-type", "text/plain")
    |> set_body("Oops! Nothing here.")
  end
end
  • A request's path is split into segments. A request to GET / has path [].

2. Running a server

To start a Raxx server a compatible HTTP server is needed. This example uses Ace that can serve both HTTP/1 and HTTP/2.

raxx_server = {MyServer, nil}
http_options = [port: 8080, cleartext: true]

{:ok, pid} = Ace.HTTP.Service.start_link(raxx_server, http_options)
  • The second element in the Raxx server tuple is passed as the second argument to the handle_request/2 callback. In this example it is unused and so set to nil.

Start your project and visit http://localhost:8080.

HTTP streaming

An HTTP exchange involves a client sending data to a server receiving a response. A simple view is to model this as a single message sent in each direction. Working with this model corresponds to Raxx.SimpleServer callbacks.

           request -->
Client ============================================ Server
                                   <-- response

When the simple model is insufficient Raxx exposes a lower model. This consists of a series of messages in each direction. Working with this model corresponds to Raxx.Server callbacks.

           tail | data(1+) | head(request) -->
Client ============================================ Server
           <-- head(response) | data(1+) | tail
  • The body of a request or a response, is the combination of all data parts sent.

Stateful server

The LongPoll server is stateful. After receiving a complete request this server has to wait for extra input before sending a response to the client.

defmodule LongPoll do
  use Raxx.Server

  @impl Raxx.Server
  def handle_head(%{method: :GET, path: ["slow"]}, state) do
    Process.send_after(self(), :reply, 30_000)

    {[], state}
  end

  @impl Raxx.Server
  def handle_info(:reply, _state) do
    response(:ok)
    |> set_header("content-type", "text/plain")
    |> set_body("Hello, Thanks for waiting.")
  end
end
  • A long lived server needs to return two things; the message parts to send, in this case nothing []; and the new state of the server, in this case no change state.
  • The initial_state is configured when the server is started.

Server streaming

The SubscribeToMessages server streams its response. The server will send the head of the response upon receiving the request. Data is sent to the client, as part of the body, when it becomes available. The response is completed when the chatroom sends a :closed message.

defmodule SubscribeToMessages do
  use Raxx.Server

  @impl Raxx.Server
  def handle_head(%{method: :GET, path: ["messages"]}, state) do
    {:ok, _} = ChatRoom.join()
    outbound = response(:ok)
    |> set_header("content-type", "text/plain")
    |> set_body(true)

    {[outbound], state}
  end

  @impl Raxx.Server
  def handle_info({ChatRoom, :closed}, state) do
    outbound = tail()

    {[outbound], state}
  end

  def handle_info({ChatRoom, data}, state) do
    outbound = data(data)

    {[outbound], state}
  end
end
  • Using set_body(true) marks that the response has a body that it is not yet known.
  • A stream must have a tail to complete, metadata added here will be sent as trailers.

Client streaming

The Upload server writes data to a file as it is received. Only once the complete request has been received is a response sent.

defmodule Upload do
  use Raxx.Server

  @impl Raxx.Server
  def handle_head(%{method: :PUT, path: ["upload"] body: true}, _state) do
    {:ok, io_device} = File.open("my/path")
    {[], {:file, device}}
  end

  @impl Raxx.Server
  def handle_data(data, state = {:file, device}) do
    IO.write(device, data)
    {[], state}
  end

  @impl Raxx.Server
  def handle_tail(_trailers, state) do
    response(:see_other)
    |> set_header("location", "/")
  end
end
  • A body may arrive split by packets, chunks or frames. handle_data will be invoked as each part arrives. An application should never assume how a body will be broken into data parts.

Request/Response flow

It is worth noting what guarantees are given on the request parts passed to the Server's handle_* functions. It depends on the Server type, Raxx.Server vs Raxx.SimpleServer:

request flow

So, for example, after a %Raxx.Request{body: false} is passed to a Server's c:Raxx.Server.handle_head/2 callback, no further request parts will be passed to to the server (c:Raxx.Server.handle_info/2 messages might be, though).

Similarly, these are the valid sequences of the response parts returned from the Servers:

response flow

Any Raxx.Middlewares should follow the same logic.

Router

The Raxx.Router can be used to match requests to specific server modules.

defmodule MyApp do
  use Raxx.Server

  use Raxx.Router, [
    {%{method: :GET, path: []}, HomePage},
    {%{method: :GET, path: ["slow"]}, LongPoll},
    {%{method: :GET, path: ["messages"]}, SubscribeToMessages},
    {%{method: :PUT, path: ["upload"]}, Upload},
    {_, NotFoundPage}
  ]
end

raxx's People

Contributors

arcz avatar charlesokwuagwu avatar chx avatar crowdhailer avatar girishramnani avatar ktec avatar lasseebert avatar nietaki avatar sheeldotme avatar varnerac avatar wayann avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

raxx's Issues

be able to make a stack of middleware

I think there must be some great macro stuff possible here to reduce runtime lookups

defmodule MyApp do
  use Raxx.Stack

  stack Raxx.Csrf
  stack Raxx.Session, secret: "top secret"
  stack Raxx.GZip
  stack Raxx.Time
  stack Raxx.WhoAmI, server_name: "my name"
  stack Raxx.ContentLength
  stack Raxx.Head
  stack Raxx.Scrub, (redirect or rewrite)
  
  mount "/assets", AssetsController
  service "/accounts", "accounts.app.consul"
end

URI query decoding.

I am unclear why elixirs URL library does not support lists when encoding and decoding query strings

URI.decode_query("foo[]=1&foo[]=2")
# %{"foo[]" => "2"}
# But I would expect %{"foo" => ["1", "2"]} 

This is obviously a deliberate choice as in the docs is this quote

Keys and values can be any term that implements the String.Chars protocol, except lists which are explicitly forbidden.

elixir-lang/elixir#2876 Suggests just using plug but I will be instead copy and pasting as raxx cannot depend on plug.

separate projects for hex

so far

  • core (raxx)
  • server_sent_events (SSE)
  • chunked (possibly core)
  • test (might just be methods eg Raxx.get
  • error handler and debug page
  • static
  • session
  • validate to check server implementations
  • ace_http (maybe move ace itself as part of the project)

have some master test file for integrations

rename helper functions

Raxx.ServerSentEvents -> Rack.ServerSentEvents.Handler

Raxx.Handler -> Raxx.{Basic/Core/Request}.Handler

Add middleware Raxx.Logger

This should be started in a new repo and linked from the extensions section of this repo.
The hex project should be raxx_logger

Retire http_status

This is a refactoring issue.

The functionality in http_status should be contained with in the core raxx project. there is no need to have it separate. Also a reason phrase is not part of the HTTP/2 specification.

The function for generating a response start line should also not add a newline character.

The http_status package should be retired on hex.

virtual hosting and the mount field

Host header

All information in the host header is duplicated in other parts the request struct.
The Host header is always required.

Therefore should the host header be deleted from this list of headers?
It should never be relied on and users building request might add a host field but not a host header

Virtual host

Can the Host header have a path in the URL?

Raxx.Request.host + Raxx.Request.mount = virtual host

Router.host instead of Router.mount

In HTTP/2 can the authority header have a path component

Version 1.0

List of decisions to be made before releasing 1.0

  • Is handle_headers a confusing name for a callback? I think perhaps the following might be clearer
    • handle_head/2
    • handle_body/2
    • handle_tail/2
    • handle_info/2
  • Deprecate named header modules, Raxx.Server and Raxx.TransferEncoding
  • ?? Make it a requirement to always have a tail Raxx.Fragment does not need end_stream attribute
  • ?? Rename Raxx.Fragment to Raxx.Body
  • ?? Rename Raxx.Server to Raxx.Spec
  • Look to increase places used.
    • Publish Apple Push Notification Client using Ace and Raxx
    • GRPC implementation with GRPC.Request subset of Raxx.Request
    • GraphQL

Streaming

  • long poll

Long poll should just await within function

  • chunked responses
  • chunked requests

https://24ways.org/2016/http2-server-push-and-service-workers/
https://github.com/phoenixframework/phoenix/blob/v1.2.3/lib/phoenix/transports/long_poll.ex#L1

[state, [{target, message]

%Response{state: :completed, promises: []}
middleware to fetch promises from response links

(server-process) -> (handler-process)
kill handler process after replying.
Good for HTTP2

@expect-continue true
def handle_request(r, c) do
   body = body(r) # memoised
   
   {{__MODULE__, c}, [%Response{body: nil}] # chunked
   {{__MODULE__, c}, []} # longpoll
   {nil, [%Response{body: nil}, promise("/favicon.ico")] # with server push
end

with mailbox monad

def handle_request(r, c) do
  reply(response)
  promise("favicon.ico")
end

def handle_request(r, c) do
  upgrade(__MODULE__, :awaiting)
end

def handle_info(message) do
  reply(response)
end
defmodule Ping.Ready do
  receive config do
    Request(path: ["event", id]) ->
      send(MyApp.PubSub, {self, id})
      send(:timer, 10_000)
      Ping.Awaiting, config
  end
end
defmodule Ping.Awaiting do
  receive config do
    PubSub.update(contents) ->
      send(MyApp.PubSub, {self, id})
      reply(ok("content"))
      Raxx.Completed # could call a close state here
  end
end

Implement headers

headers should be an extensible concept.

Consider having a module for each header eg Raxx.ContentType then other libraries can just implement Raxx.XMyHeader

  • Should we implement a behaviour module for a generic header?

core headers

Debugger in dev mode

Take the better errors implementation in plug and port to raxx. Would be nice if can be first example of pluggable error handler. #8

Error Handlers

Have an extensible method for handling errors within the main callbacks.

Do we need different error handlers for different call backs. Or can we just have a return value of resolved/not_resolved

add protocol field to request

destructing of the Host field gives host and port but also protocol and possibly authority. Though I think that authority is not part of the updated rfc.

Raise error for incomplete response without new server state

If any callback returns a response with body set to true an error should be raised. This is because the response is incomplete.

e.g.

defmodule MyServer do
use Raxx.Server

def handle_request(request, state) do
  response = Raxx.response(:ok)
  |> Raxx.set_body(true)
end
end

This example should raise an error
the correct return value is {response, new_state}

Add type specs and dialyzer

Add dialyxir to this project and typespecs to all public functions. The benefits of this are improved documentation as well as an extra layer of checks.

Running dialyzer should become part of the travis test action.

Add @impl

All examples should have @impl Raxx.Server used correctly through out the project

pipe get_content through stack of parsers

copy from plug.

also use the pattern

{buffer, partial}

Can be a middleware layer.

parsing as a stream to go with other middleware stuff. return a list so buffer can contain many parts

Fail build if code is not formatted

This will have to wait until the new year when elixir 1.6 is released. The CI tests should fail if running the formatter would result in a change to the code.

Not sure if such a mix task even exists, but I think it would be good. It would however mean that raxx required 1.6 to work.

use

mix format --check-formatted mix.exs "{lib,test}/**/*.{ex,exs}"

multi line server sent events

Should be able to send data content with newline characters in it.

Best to testby property testing encode/decode.

Raxx summary function

for logging really.

maybe called inspect

get "/path" (Host: example.com) (Cookie: foo=bar)

setting up raxx for ssl

Thanks for this simple web server library.

Please can you give an example of how to setup raxx as a secure server, i.e. with ssl.
I have looked at your Elli and Cowboy adaptors, I cannot find where you handle https.

Add documentation to raxx_cowboy

  • Add documentation to README
  • Ensue Docs available on hex
  • Improve hello_cowboy example, ideally add to supervision tree #31
  • Link from top level README

Use logger

Use logger not IO.inspect on adapter.

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.