Code Monkey home page Code Monkey logo

grip's Introduction

The microframework for writing powerful web applications.

Actions Status

TechEmpower Benchmark

Docs CI Status

Chat on Gitter

Grip is a microframework for building RESTful web applications. It is designed to be modular and easy to use, with the ability to scale up to the limits of the Crystal programming language. It offers extensibility and has integrated middleware called "pipes". Pipes can alter parts of the request/response context and then get passed to the actual endpoint. Grip's router is very similar to the router of the Phoenix framework. And most of all: Grip is fast.

Motivation

This project exists due to the fact that Kemal lacks one crucial part of a framework, a structure. An example for the absence of a structure can be found here.

Features

  • HTTP 1.1 support.
  • WebSocket RFC 6455 support.
  • Built-in exceptions support.
  • Parameter handling support.
  • JSON serialization and deserialization support (fastest framework with JSON in Crystal).
  • Middleware support.
  • Request/Response context, inspired by expressjs.
  • Advanced routing support.

Code example

Add this to your application's application.cr:

require "grip"

class IndexController < Grip::Controllers::Http
  def get(context : Context) : Context
    context
      .put_status(200) # Assign the status code to 200 OK.
      .json({"id" => 1}) # Respond with JSON content.
      .halt # Close the connection.
  end

  def index(context : Context) : Context
    id =
      context
        .fetch_path_params
        .["id"]

    # An optional secondary argument gives a custom `Content-Type` header to the response.
    context
      .json(content: {"id" => id}, content_type: "application/json; charset=us-ascii")
      .halt
  end
end

class Application < Grip::Application
  def initialize(environment : String, serve_static : Bool)
    # By default the environment is set to "development" and serve_static is false.
    super(environment, serve_static)

    scope "/api" do
      scope "/v1" do
        get "/", IndexController
        get "/:id", IndexController, as: :index
      end
    end

    # Enable request/response logging.
    router.insert(0, Grip::Handlers::Log.new)
  end
end

app = Application.new(environment: "development", serve_static: false)
app.run

Installation

Add this to your application's shard.yml:

dependencies:
  grip:
    github: grip-framework/grip

And run this command in your terminal:

shards install

API Reference

Documentation can be found on the official website of the Grip framework or the CrystalDoc website.

Contribute

See our contribution guidelines and read the code of conduct.

grip's People

Contributors

caustickirbyz avatar coralpink-d2e1ac26 avatar cyangle avatar danreeves avatar grkek avatar marcoroth avatar nilsding avatar treagod avatar vinyll avatar whaxion avatar yanecc 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  avatar

grip's Issues

Grip::Pipes::Log show/print only HTTP status 200 requests

Hi,

first of all I would like to thank you for awesome work on the Grip project.

I followed the example #17 and figured out that Grip::Pipes::Log show/print only HTTP status 200 requests. Is there an option to print unsuccessful requests through pipeline logger?

Handle arbitrary exceptions

@grkek, I want to catch JSON::SerializationError without specifying begin โ€ฆ rescue โ€ฆ end block everywhere.
I read the source, and it seems it's not possible currently.
I am interested in implementing this. I have two ideas. If you agree, I can implement this.

Approach 1

What do you think about changing the @handlers keys to be the type of Exception instead of status code?
Currently, custom exceptions have a @status code, and this is what is matched in the handlers.
I would create a base class Grip::Exception::StatusException < Exception, then inherit 404Error, 500Error, etc.
This is how it would be used:

error 404Error, My404Page
error 500Error, My500Page
error JSON::SerializationError, JsonErrorPage

Approach 2

Another approach could be to rename @handlers to @status_handlers, and create an @exception_handlers too.
Then the matching would be done on exception type. If it is a Grip::Exceptions::Base, then the current status code matching behavior would activate. This could be done by setting up a handler by default for Grip::Exceptions::Base, which would decide what page is rendered by matching @status_handlers using the currently implemented method.
I think this approach could be done without breaking existing code, as the error macro could decide which handler is set up (status or exception).

error 404, My404Page # This is an Int32, then set up status code handler
error DivisionByZero, MyCalculatorErrorPage # This is an Exception, then set up exception handler

extension on response causes unflushed io

Description

IO doesn't flush on close

Steps to Reproduce

class Index < Grip::Controller::Http
  def get(context)
    text(
      context,
      "-"*10000 + "!"
    )
  end
end

class Application < Grip::Application
  def initialize
    get "/", Index
  end
end

app = Application.new
app.run

Expected behavior:

using curl http://localhost:3000/ the response should be closed and the ! should be returned

Actual behavior:

using curl http://localhost:3000/ the response is not closed and the ! is never printed

Reproduces how often:

always

I narrowed it done to the extension on response
The original implementation shows
https://github.com/crystal-lang/crystal/blob/5999ae29b/src/http/server/response.cr#L222

But the grip redefined method does not flush the IO.
Looking at the methods they seem pretty similar, is this extension still required?

Error: execution of command failed with exit status 1

Hi! Love your work on the framework! Great design decisions!
I've tried to install and run on local, and run into trouble:

$ crystal -v
Crystal 1.9.2 [1908c816f] (2023-07-19)

LLVM: 15.0.7
Default target: aarch64-apple-darwin

$ shards install
Resolving dependencies
Fetching https://github.com/grip-framework/grip.git
Fetching https://github.com/luislavena/radix.git
Fetching https://github.com/crystal-loot/exception_page.git
Fetching https://github.com/sija/backtracer.cr.git
Using radix (0.4.1)
Using backtracer (1.2.2)
Using exception_page (0.3.1)
Using grip (2.0.2)

$ crystal run src/demo.cr
ld: library not found for -lssl (this usually means you need to install the development package for libssl)
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Error: execution of command failed with exit status 1: cc "${@}" -o /Users/tolik.kukul/.cache/crystal/crystal-run-demo.tmp  -rdynamic -L/Users/tolik.kukul/.asdf/installs/crystal/1.9.2/embedded/lib -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lpcre2-8 -lgc -levent -liconv

I've reinstalled openssl and tried to set the var like they suggest on Crystal website, but still error. Please help! Thank you!

Route with override would cause instantiating new instance of controller for each request

I might be wrong, but it seems like the route override Proc would instantiate a new controller instance for each request.

The instantiation happens in the override Proc:

\{{ resource }}.new.as(\{{resource}}).\{{kwargs[:as].id}}(context)

When there's no method override, it uses the controller instance saved in the route instance:

payload.handler.call(context)

Ideally, the override should also use the controller instance saved in the route instance.

This behavior could be observed with below example server:

require "../src/grip"

class IndexController < Grip::Controllers::Http
  def initialize
    puts "Initializing Index Controller"
  end

  def get(context : Context) : Context
    puts "info"
    context
      .put_status(200) # Assign the status code to 200 OK.
      .json({"id" => 1}) # Respond with JSON content.
      .halt # Close the connection.
  end

  def index(context : Context) : Context
    puts "echo"
    id =
      context
        .fetch_path_params
        .["id"]

    # An optional secondary argument gives a custom `Content-Type` header to the response.
    context
      .json(content: {"id" => id}, content_type: "application/json; charset=us-ascii")
  end
end

class Application < Grip::Application
  def routes
    get "/info", IndexController
    get "/echo/:id", IndexController, as: :index
  end
end

app = Application.new
app.run

The log shows below messages:

Initializing Index Controller
Initializing Index Controller
2022-02-02 07:58:42 UTC [info] listening at http://0.0.0.0:5000.
info
info
info
Initializing Index Controller
echo
Initializing Index Controller
echo
Initializing Index Controller
echo

I think we should avoid instantiating new instances of the controllers for each request.
@grkek

Undefined method 'final' for OpenSSL::Digest

Describe the bug
When trying to run a Grip application, it can't be compiled because the method final is not defined for OpenSSL::Digest.

To Reproduce
Steps to reproduce the behavior:

  1. Start a new crystal project with crystal init app <app_name>
  2. Add Grip dependency to shards.yml, exactly like in the README
  3. Copy & Paste the Grip example from the readme to your code
  4. See error

Expected behavior
Have a running Grip server running

Desktop (please complete the following information):

  • OS: Ubuntu 20.04
  • Crystal 0.34.0

Main differences between v0.28.x and v1.x.x

Hello, first of all I want to thank you for the awesome work on Grip!

The version I use in my projects is the v0.28.3, and I recently saw some movement on this repo, leading to release of v1.0.0 recently.

Just got curious about which features and breaking changes there are on this new major version? What are the main differences that v1.0.0 brings in?

Add spec-grip to grip-framework

Hello, I've made spec-grip and I want it to be in grip-framework so, is it possible to add me to the org like that I transfer to grip-framework ?
Thanks! ๐Ÿ˜ƒ

SHA1 Alternatives

Hello,
I was going through the code looking for how SSL is used internally and I noticed that SHA1 is explicitly required in the project. SHA1 is no longer secure, so can there be a way to choose the hashing method to be used?

Thanks in advance

Built in static asset server

Are there any plans to include a static file server in grip? Similar to how Kemal automatically serves the /public directory or Phoenix serves the priv/static/ directory.

JSON error bug

So I'm having a problem passing the context.fetch_body_params function to context.json()

I wrote the code just like in the documentation example:

def get(context : Context) : Context
  params =
    context
      .fetch_body_params

  context
    .json(params)
end

And it's giving me this error:

Error

I'm sorry if it's my own mistake

Wrong behavoir of serve_static on Windows

On Windows, when serve_static is set true, behaviors to request for local resources will lead to a series of errors, including 304 resp status to a request for static files, none response for requests to obtain data from database etc.

expanded_path = File.expand_path(request_path, "/")

File.expand_path("/whois", "/") => C:\whois
This may be part of the cause of the problem.

TechEmpower benchmark is broken

Just thought you might want to know since I'm working on adding a different framework and noticed.

grip: In grip.cr:48:14
grip:  48 | class Json < Grip::Controller::Http
grip:                    ^---------------------
grip: Error: undefined constant Grip::Controller::Http
grip: Did you mean 'Grip::Controllers'?

Rendering HTML does not have a default encoding

The following code

class Index < Grip::Controllers::Http
  def get(context : HTTP::Server::Context)
    context
      .html("<h1>Hello world ๐Ÿ˜„</h1>")
      .halt
  end

Should render <h1>Hello world ๐Ÿ˜„</h1>

Current rendering: <h1>Hello world รฐ๏ฟฝ๏ฟฝ๏ฟฝ</h1>


In order to do so, the .html() method could set the default encoding to utf-8.

In the meantime, skipping the shortcut does the job:

class Index < Grip::Controllers::Http
  def get(context : HTTP::Server::Context)
    context
      .put_resp_header("Content-Type", "text/html; charset=utf-8")
      .send_resp("<h1>Hello world ๐Ÿ˜„</h1>")
      .halt
  end

Feature request: File upload/download

Is your feature request related to a problem? Please describe.
Helper methods to handle file upload/download requests
Helper methods or documentation to handle streaming video/audio

Currently there's no documentation for how to upload/download files

Describe the solution you'd like
Have something similar to Kemal's send_file helper method in Controller class and documentation of example usages.

Describe alternatives you've considered

Additional context
It needs to handle uploading/downloading files and possibly even streaming for a video/audio website.

Custom error handling and compile error

Hi,

I'm checking the example on https://grip-framework.github.io/docs/error_handling/ and when I run the test app, I get the next error:

There was a problem expanding macro 'error'

Code in src/napake.cr:39:5

39 | error 403, ForbiddenController
^
Called macro defined in lib/grip/src/grip/dsl/macros.cr:82:7

82 | macro error(error_code, resource)

Which expanded to:

1 | @exception_handler.handlers[{{error_code}}] = {{resource}}.new
^---------
Error: undefined macro variable 'error_code'

I also tested the code(custom error pages) with my old examples from version 1.1.1 and the results are practically the same.

br, uz

Do you accept contributions?

@grkek I took a look into the source code, and it seems it is using older approaches, or solutions that were necessary with an older version of the standard library.
I want to contribute some improvements, but I only open PR if I know that it will get attention.

For example, Grip::Parsers::ParameterBox::AllParamsType is unnecessary as it's the same as the JSON::Any::Type in the latest version of the standard library.

Also, the standard library includes a HTTP::Request#query_params in the latest version.

There are some places in the documentation that could be improved also.

Index syntax

Why does Grip have so ugly method names like context.get_resp_headers("Content-Type")?
Idiomatic Crystal code should use the indexing syntax: context.response.headers["Content-Type"]

Better json format

Is there a better way to get a better json format, in grip? without these "/"

Here is my code :
image

and this is the response:
image

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.