Code Monkey home page Code Monkey logo

http's Introduction

Onyx::HTTP

Built with Crystal Travis CI build Docs API docs Latest release

An opinionated framework for scalable web.

About 👋

Onyx::HTTP is an opinionated HTTP framework for Crystal language. It features DSL and modules to build modern, scalabale web applications with first-class support for websockets.

Installation 📥

Add these lines to your application's shard.yml:

dependencies:
  onyx:
    github: onyxframework/onyx
    version: ~> 0.6.0
  onyx-http:
    github: onyxframework/http
    version: ~> 0.9.0

This shard follows Semantic Versioning v2.0.0, so check releases and change the version accordingly.

Note that until Crystal is officially released, this shard would be in beta state (0.*.*), with every minor release considered breaking. For example, 0.1.00.2.0 is breaking and 0.1.00.1.1 is not.

Usage 💻

The simplest hello world:

require "onyx/http"

Onyx::HTTP.get "/" do |env|
  env.response << "Hello, world!"
end

Onyx::HTTP.listen

Encapsulated endpoints:

struct GetUser
  include Onyx::HTTP::Endpoint

  params do
    path do
      type id : Int32
    end
  end

  errors do
    type UserNotFound(404)
  end

  def call
    user = Onyx::SQL.query(User.where(id: params.path.id)).first? # This code is part of onyx/sql
    raise UserNotFound.new unless user

    return UserView.new(user)
  end
end

Onyx::HTTP.get "/users/:id", GetUser

Encapsulated views:

struct UserView
  include Onyx::HTTP::View

  def initialize(@user : User)
  end

  json id: @user.id, name: @user.name
end

Websocket channels:

struct Echo
  include Onyx::HTTP::Channel

  def on_message(message)
    socket.send(message)
  end
end

Onyx::HTTP.ws "/", Echo

Documentation 📚

The documentation is available online at docs.onyxframework.org/http.

Community 🍪

There are multiple places to talk about Onyx:

Support ❤️

This shard is maintained by me, Vlad Faust, a passionate developer with years of programming and product experience. I love creating Open-Source and I want to be able to work full-time on Open-Source projects.

I will do my best to answer your questions in the free communication channels above, but if you want prioritized support, then please consider becoming my patron. Your issues will be labeled with your patronage status, and if you have a sponsor tier, then you and your team be able to communicate with me privately in Twist. There are other perks to consider, so please, don't hesistate to check my Patreon page:

You could also help me a lot if you leave a star to this GitHub repository and spread the word about Crystal and Onyx! 📣

Contributing

  1. Fork it ( https://github.com/onyxframework/http/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'feat: some feature') using Angular style commits
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors

Licensing

This software is licensed under MIT License.

Open Source Initiative

http's People

Contributors

vladfaust 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

http's Issues

Allow kebab-case params

Currently this does not compile:

params do
  param :"my-param", String?
end

With following error:

Syntax error in expanded macro: define_params_tuple:9: expected ')' or named argument, not g

          g-recaptcha-response: String
          ^

Which is kinda expected.

Custom params validation

def foo_valid?(foo)
  some_code?(foo)
end

params do
  param :id, Int32, ->{ p > 0 }
  param :foo, String?, validate: foo_valid?
end

Cookies example

I'm guessing there's a straightforward way of handling this but everything I end up with feels a little overcomplicated or sloppy, typically via HTTP::Cookies and the Set-Cookie header. Is there a nicer way of handling this?

Would this a good candidate for a documentation example? Or even some kind of helper module?

Halting with JSON

These halts should cast an object to JSON and change content type:

halt!(409, {error: "Something went wrong"})
halt!(403, PaymentError.new)

Add key aliases for params

params do
  type stripe_source : String, key: :stripeSource
end

So, a request of ?stripeSource=foo would propagate params with params[:stripe_source].

Adapt the project for newcomers

Crystal is growing. I spend so much effort on Prism to make web-development a joy, but seems like I'm the one who's using Prism in production. Let's change it!

  • Remind that the GitHub README is for the master branch
  • Tell that features appear along my needs and I'm open for issues and PRs
  • Prism needs a website
    • Fancy landing page
    • Docs either on GitBook or VuePress
  • Adapt for developers new to Crystal
    • Benchmarks with other languages
    • Common frameworks code comparison
    • Note about "catch bugs in development, not in production"
    • Tell about websockets built-in
  • Adapt for Crystal developers looking for a better web framework
    • Tell about Prism modularity when you decide what to include
    • Tell about Prism object-oriented approach when where are no global configuration objects which allows to have multiple servers and routers in one application
    • Tell about versatile callbacks system
    • Tell about strict params typecasting which reduces amount of code and improves security drastically
    • Tell about versatile authentication module
  • Add project template

Web Sockets accessible only via ws:// scheme

Should check incoming request's scheme and organize the routing tree with /ws instead of current /get.

This will allow to place websocket actions under the same routes. 👍

Ruby-ish style callbacks

Current:

def before
  super
  some_code
end

Proposed:

before do
  some_code
end

before do
  another_code
end

Allow to accept empty params

params do
  type email : String | Null?
end

def call
  case params[:email]
  when Null
    user.email = nil
  when String
    user.email = params[:email]
  end
end

Nested params

params do
  param :user do
    param :email, String
    param :password, String
  end
end

Extract params to an external shard

Prism is famous for its strict params typing allowing nesting and array params as well. I'm proud of that and it's initially has been a goal for this project - typed params.

I know that other frameworks lack in this type of functionality and I'd like to extract it into a separate shard.

@paulsmith said that it would be better to make Params a class and make all params accessible by a method (e.g. params.user.email). Also there was a thought of making it abstract so it works not only with HTTP params, but with other data inputs.

How do you see an ideal shard for such a functionality? Take your time, see the code (start from https://github.com/vladfaust/prism/blob/master/src/prism/params.cr).

/cc @drujensen

Change params DSL

Old:

params do
  param :email, String, validate: {regex: /@/, size: (5..64)}
  param :password, String, validate: {size: (8..64)}
end

New:

params do
  type email : String, validate: {regex: /@/, size: (5..64)}
  type password : String, validate: {size: (8..64)}
end

Pros:

  • Canonical type definition
  • Highlighting
  • Neat indentation (notice how params is indented with type 😏)
  • Editors do not offer param on typing within def call, this is annoying

RFC on keyword though!

Make logged content a bit more abstact

class MyLogger < Rest::Logger
  def content(context)
    params = context.request.query_params.to_h
    somehow_modify_params # For example, cut some sensitive data
    context.request.path + modified_params # By default it's context.resource
  end
end

# GET /path?foo=<#> 1s

or even

logger = Rest::Logger.new do |env|
  context.request.path
end

# GET /path 1s

Complex types params

Cast String to Array or Hash, e.g:

params do
  param :ids, Array(String)
end

The issue is that different content types have different ways to pass arrays (and hashes). Moreover, there is no single standard of how-to... 🤔

Rename `from_s`

A program could potentially have many ways to initialize types from String, e.g. Int32 could be initialized like Int32.from_s("one") in some code.

I should rename from_s to something like from_string_param or maybe introduce initializers which accept String.

Export routes with params

struct UpdateUser
  params do
    type id : Int32
    type name : String, description: "The name of the user"
  end
end

router = Atom::Handlers::Router.new do
  patch "/users/:id", UpdateUser
end
app --routes
PATCH /users/:id
   ├─ id : Int32
   └─ name : String (The name of the user)

Add Router#on

router = Rest::Router.new do |r|
  r.on "/foo", methods: %w(get put post) do |env|
    MyAction.call(env)
  end
end

Faster router

Before 46e9ec7 (it used Router::Cacher):

faust@mi:~/Projects/prism$ crystal bench/router.cr --release --no-debug

Begin benchmarking router...
Running static paths...

simply cached router with static path   2.72M (367.77ns) (±17.02%)  207 B/op        fastest
   non-cached router with static path   1.41M (706.98ns) (± 8.20%)  496 B/op   1.92× slower

Running dynamic paths (random from 10000 known paths)...

simply cached router with dynamic paths 954.07k (  1.05µs) (±11.87%)   848 B/op        fastest
   non-cached router with dynamic paths 701.96k (  1.42µs) (±16.06%)  1296 B/op   1.36× slower

Running absolutely random paths (caching is useless)...

simply cached router with random paths 279.56k (  3.58µs) (±20.55%)  1449 B/op   2.09× slower
   non-cached router with random paths 584.69k (  1.71µs) (±19.35%)  1365 B/op        fastest

✔️ Done benchmarking router

After 46e9ec7:

faust@mi:~/Projects/prism$ crystal bench/router.cr --release --no-debug
  with static path    3.0M (333.73ns) (± 8.66%)   194 B/op        fastest
with dynamic paths 777.92k (  1.29µs) (±12.42%)  1400 B/op   3.85× slower
 with random paths 628.63k (  1.59µs) (± 8.63%)  1317 B/op   4.77× slower

Error: Prism::Action is not a module, it's a struct

Running the basic example:

require "prism"

struct KnockKnock
  include Prism::Action
  include Prism::Action::Params

  params do
    param :who, String
    param :times, Int32, validate: {max: 10}
  end

  def call
    params[:times].times do
      text("Knock-knock #{who}\n")
    end
  end
end

router = Prism::Router.new do
  get "/:who", KnockKnock
end

logger = Logger.new(STDOUT)
log_handler = Prism::LogHandler.new(logger)
handlers = [log_handler, router]

server = Prism::Server.new(handlers, logger)
server.bind_tcp(8000)
server.listen

#  INFO -- :   Prism::Server is listening on http://127.0.0.1:5000...

Results in a error:

Error in src/simple_app.cr:4: Prism::Action is not a module, it's a struct

  include Prism::Action

Crystal Info

$ crystal -v
Crystal 0.26.0 [eeb53c506] (2018-08-09)

LLVM: 4.0.0
Default target: x86_64-unknown-linux-gnu

Change `halt!` to `break`

Old:

halt!(409, {error: "User already exists"}) if existing_user

New:

break(409, {error: "User already exists"}) if existing_user

Pros:

  • Highlighting

Action & Channel should be modules

Composition over inheritance!

Ideal code would be:

struct MyAction
  include Prism::Action
  include Prism::Params
  include Prism::Auth
  
  auth!

  params do
    param :foo, Int32
  end
  
  def call
    # ...
  end
end

That said, following must be implemented:

  • Prism::Action includes Callbacks and they do actually work (see crystal-lang/crystal#1104 (comment))
  • MyAction can be an abstract struct and callbacks should work in this case as well
  • Prism::Params and Prism::Auth included along with Prism::Action automatically inject those before callbacks Introduce Prism::Action::Params & Prism::Action::Auth, which is more straightforward IMO

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.