Go web framework with a natural feel

Fireball

MIT License Go Report Card Go Doc

Overview

Fireball is a package for Go web applications. The primary goal of this package is to make routing, response writing, and error handling as easy as possible for developers, so they can focus more on their application logic, and less on repeated patterns.

Installation

To install this package, run:

go get github.com/zpatrick/fireball

Getting Started

The following snipped shows a simple "Hello, World" application using Fireball:

package main

import (
  "github.com/zpatrick/fireball"
  "net/http"
)

func index(c *fireball.Context) (fireball.Response, error) {
  return fireball.NewResponse(200, []byte("Hello, World!"), nil), nil
}

func main() {
  indexRoute := &fireball.Route{
    Path: "/",
    Handlers: fireball.Handlers{
      "GET": index,
    },
  }

  routes := []*fireball.Route{indexRoute}
  app := fireball.NewApp(routes)
  http.ListenAndServe(":8000", app)
}

This will run a new webserver at localhost:8000

Handlers

Handlers perform the business logic associated with requests. Handlers take a Context object and returns either a Response or an error.

HTTP Response

The HTTP Response is a simple object that implements the Response interface. When the Write call is executed, the specified Body, Status, and Headers will be written to the http.ResponseWriter.

Examples:

func Index(c *fireball.Context) (fireball.Response, error) {
    return fireball.NewResponse(200, []byte("Hello, World"), nil), nil
}
func Index(c *fireball.Context) (fireball.Response, error) {
    html := []byte("<h1>Hello, World</h1>")
    return fireball.NewResponse(200, html, fireball.HTMLHeaders), nil
}

HTTP Error

If a Handler returns a non-nil error, the Fireball Application will call its ErrorHandler function. By default (if your Application object uses the DefaultErrorHandler), the Application will check if the error implements the Response interface. If so, the the error's Write function will be called. Otherwise, a 500 with the content of err.Error() will be written.

The HTTPError is a simple object that implements both the Error and Response interfaces. When the Write is executed, the specified status, error, and headers will be written to the http.ResponseWriter.

Examples:

func Index(c *fireball.Context) (fireball.Response, error) {
    return nil, fmt.Errorf("an error occurred")
}
func Index(c *fireball.Context) (fireball.Response, error) {
    if err := do(); err != nil {
        return nil, fireball.NewError(500, err, nil)
    }
    
    ...
}

Routing

Basic Router

By default, Fireball uses the BasicRouter object to match requests to Route objects. The Route's Path field determines which URL patterns should be dispached to your Route. The Route's Handlers field maps different HTTP methods to different Handlers.

You can use :variable notation in the Path to match any string that doesn't contain a "/" character. The variables defined in the Route's Path field can be accessed using the Context object.

Example:

route := &Fireball.Route{
  Path: "/users/:userID/orders/:orderID",
  Methods: fireball.Handlers{
    "GET": printUserOrder,
  },
}

func printUserOrder(c *fireball.Context) (fireball.Response, error) {
    userID := c.PathVariables["userID"]
    orderID := c.PathVariables["orderID"]
    message := fmt.Sprintf("User %s ordered item %s", userID, orderID)
    
    return fireball.NewResponse(200, []byte(message), nil)
}

Static Routing

The built-in FileServer can be used to serve static content. The follow snippet would serve files from the static directory:

  app := fireball.NewApp(...)
  http.Handle("/", app)

  fs := http.FileServer(http.Dir("static"))
  http.Handle("/static/", http.StripPrefix("/static", fs))
  
  http.ListenAndServe(":8000", nil)

If the application workspace contained:

app/
    main.go
    static/
        hello_world.txt

A request to /static/hello_world.txt would serve the desired file.

HTML Templates

By default, Fireball uses the GlobParser to render HTML templates. This object recursively searches a given directory for template files matching the given glob pattern. The default root directory is "views", and the default glob pattern is "*.html" The name of the templates are path/from/root/directory + filename.

For example, if the filesystem contained:

views/
    index.html
    partials/
        login.html

The templates names generated would be "index.html", and "partials/login.html". The Context contains a helper function, HTML, which renders templates as HTML.

Example:

func Index(c *fireball.Context) (fireball.Response, error) {
    data := "Hello, World!"
    return c.HTML(200, "index.html", data)
}

Decorators

Decorators can be used to wrap additional logic around Handlers. Fireball has some built-in decorators:

In addition to Decorators, the Before and After functions on the Application object can be used to perform logic when the request is received and after the response has been sent.

Examples & Extras

License

This work is published under the MIT license.

Please see the LICENSE file for details.

Comments
  • Add swagger plugin

    Add swagger plugin

    Add a plugin that allows users to provide a swagger webserver using fireball. It should be similar to: https://github.com/emicklei/go-restful-openapi/blob/master/examples/user-resource.go

    Initial design thoughts: users create the swagger object and it returns a Route that can serve swagger requests:

    func main() {
        swagger := fireball.Swagger {
            Routes: []fireball.SwaggerRoute{
                {
                    Path: "/people",
                    Method: "GET",
                    Returns: []Person{},
                 },
                 {
                    Path: "/people/:name",
                    Method: "GET",
                    PathParams: []swagger.PathParam{
                        {
                             Name: "name",
                             Description: "Name of the person",
                        },
                    },
                    Returns: Person{},
                 },
            },
        }
        
        swaggerRoute := swagger.Route()
    
        routes := []*fireball.Route{swaggerRoute}
        app := fireball.NewApp(routes)
    }
    

    This could also be a generic, standalone repository that make it easy to serve using whichever mux you want, given it has a ServeHTTP function - e.g.:

        swagger := &swagger.Swagger{
            Routes: []swagger.Routes{},
            ...
        }
    
        http.Serve(swagger)
    

    ... or in fireball:

    func ServeSwagger(c *fireball.Context) (fireball.Response, error) {
        response := fireball.ResponseFunc(func(w http.ResponseWriter, r *http.Request) {
            swagger.ServeHTTP(w, r)
        })
    
        return response, nil
    }
    
    
  • Fatal error: concurrent map writes

    Fatal error: concurrent map writes

    There are intermittent concurrent map write errors that occur when using fireball router. The stack trace below, indicates it is occurring with the route cache logic. I was able to reproduce it by sending a lot of GET requests to an endpoint. But is is an intermittent error so I don't know how much benefit there maybe to get consistent reproducible steps (if it is even possible).

    2018/03/27 22:22:19 [INFO] Listening on port 9090
    fatal error: concurrent map writes
    fatal error: concurrent map writes
    
    goroutine 87 [running]:
    runtime.throw(0x1b01774, 0x15)
            /usr/local/Cellar/go/1.10/libexec/src/runtime/panic.go:619 +0x81 fp=0xc420269c00 sp=0xc420269be0 pc=0x102b361
    runtime.mapassign_faststr(0x1942440, 0xc420349410, 0xc42034e048, 0x4, 0x22c2720)
            /usr/local/Cellar/go/1.10/libexec/src/runtime/hashmap_fast.go:703 +0x3e9 fp=0xc420269c70 sp=0xc420269c00 pc=0x100d5c9
    github.com/quintilesims/layer0/vendor/github.com/zpatrick/fireball.(*BasicRouter).Match(0xc420399280, 0xc42042a000, 0x1bb0360, 0xc4203782a0, 0xc42042a000)
            /Users/schemudugunta/go/src/github.com/quintilesims/layer0/vendor/github.com/zpatrick/fireball/router.go:54 +0x10d fp=0xc420269cd0 sp=0xc420269c70 pc=0x140d6cd
    github.com/quintilesims/layer0/vendor/github.com/zpatrick/fireball.(*App).ServeHTTP(0xc4204de380, 0x1bb0360, 0xc4203782a0, 0xc42042a000)
            /Users/schemudugunta/go/src/github.com/quintilesims/layer0/vendor/github.com/zpatrick/fireball/app.go:40 +0xac fp=0xc420269d18 sp=0xc420269cd0 pc=0x140c4ec
    net/http.(*ServeMux).ServeHTTP(0x22a4020, 0x1bb0360, 0xc4203782a0, 0xc42042a000)
            /usr/local/Cellar/go/1.10/libexec/src/net/http/server.go:2337 +0x130 fp=0xc420269d58 sp=0xc420269d18 pc=0x1265f50
    net/http.serverHandler.ServeHTTP(0xc420445ee0, 0x1bb0360, 0xc4203782a0,0xc42042a000)
            /usr/local/Cellar/go/1.10/libexec/src/net/http/server.go:2694 +0xbc fp=0xc420269d88 sp=0xc420269d58 pc=0x1266ffc
    net/http.(*conn).serve(0xc4205ce500, 0x1bb09a0, 0xc4204ac040)
            /usr/local/Cellar/go/1.10/libexec/src/net/http/server.go:1830 +0x651 fp=0xc420269fc8 sp=0xc420269d88 pc=0x1263201
    runtime.goexit()
    

    The underlying logic that does maps writes seems to be incorrect as it does not account for concurrent map writes. This blog post seems to have good information on how to handle concurrent access to a map, https://blog.golang.org/go-maps-in-action.

    Specifically using a sync.RWMutex

    // This statement declares a counter variable that is an anonymous struct 
    // containing a map and an embedded sync.RWMutex.
    var counter = struct{
        sync.RWMutex
        m map[string]int
    }{m: make(map[string]int)}
    
    // To read from the counter, take the read lock:
    counter.RLock()
    n := counter.m["some_key"]
    counter.RUnlock()
    fmt.Println("some_key:", n)
    
    // To write to the counter, take the write lock:
    counter.Lock()
    counter.m["some_key"]++
    counter.Unlock()
    
  • Create a type fireball.Handlers

    Create a type fireball.Handlers

    If you create:

    type Handlers map[string]Handler

    Then you can write:

    indexRoute := &fireball.Route{ Path: "/", Handlers: fireball.Handlers{ "GET": index, }, }

  • Default Router needs to be thread safe

    Default Router needs to be thread safe

    The map in the router can hit concurrent writes. This needs to be thread safe:

    goroutine 2145 [running]:
    runtime.throw(0xeadf3c, 0x15)
    	/home/jparsons/Downloads/go/src/runtime/panic.go:605 +0x95 fp=0xc4204f5ba8 sp=0xc4204f5b88 pc=0x42a335
    runtime.mapassign_faststr(0xd16c20, 0xc4201f3a70, 0xc420222040, 0x3e, 0x15ffae0)
    	/home/jparsons/Downloads/go/src/runtime/hashmap_fast.go:607 +0x4f5 fp=0xc4204f5c28 sp=0xc4204f5ba8 pc=0x40cc25
    github.com/quintilesims/layer0/vendor/github.com/zpatrick/fireball.(*BasicRouter).Match(0xc4203d7160, 0xc4201a6d00, 0x159f100, 0xc42034a7e0, 0xc4201a6d00)
    	/home/jparsons/dev/go/src/github.com/quintilesims/layer0/vendor/github.com/zpatrick/fireball/router.go:54 +0x110 fp=0xc4204f5c88 sp=0xc4204f5c28 pc=0x81d640
    github.com/quintilesims/layer0/vendor/github.com/zpatrick/fireball.(*App).ServeHTTP(0xc4201ee480, 0x159f100, 0xc42034a7e0, 0xc4201a6d00)
    	/home/jparsons/dev/go/src/github.com/quintilesims/layer0/vendor/github.com/zpatrick/fireball/app.go:40 +0xac fp=0xc4204f5cd8 sp=0xc4204f5c88 pc=0x81bcec
    net/http.(*ServeMux).ServeHTTP(0x15df200, 0x159f100, 0xc42034a7e0, 0xc4201a6d00)
    	/home/jparsons/Downloads/go/src/net/http/server.go:2254 +0x130 fp=0xc4204f5d18 sp=0xc4204f5cd8 pc=0x65bad0
    net/http.serverHandler.ServeHTTP(0xc4203645b0, 0x159f100, 0xc42034a7e0, 0xc4201a6d00)
    	/home/jparsons/Downloads/go/src/net/http/server.go:2619 +0xb4 fp=0xc4204f5d48 sp=0xc4204f5d18 pc=0x65d044
    net/http.(*conn).serve(0xc420645220, 0x159fac0, 0xc420059840)
    	/home/jparsons/Downloads/go/src/net/http/server.go:1801 +0x71d fp=0xc4204f5fc8 sp=0xc4204f5d48 pc=0x6591ed
    runtime.goexit()
    	/home/jparsons/Downloads/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc4204f5fd0 sp=0xc4204f5fc8 pc=0x457ca1
    created by net/http.(*Server).Serve
    	/home/jparsons/Downloads/go/src/net/http/server.go:2720 +0x288
    
  • url with trailingslash /user/100/   match Path:

    url with trailingslash /user/100/ match Path: "/user/:id" shoud return 404

    package main
    
    import (
    	"github.com/zpatrick/fireball"
    	"net/http"
    )
    
    func index(c *fireball.Context) (fireball.Response, error) {
    	return fireball.NewResponse(200, []byte("Hello, World!"), nil), nil
    }
    
    func main() {
    	indexRoute := &fireball.Route{
    		Path: "/user/:id",
    		Handlers: fireball.Handlers{
    			"GET": index,
    		},
    	}
    
    	routes := []*fireball.Route{indexRoute}
    	app := fireball.NewApp(routes)
    	http.ListenAndServe(":8000", app)
    }
    
    

    $ curl http://localhost:8000/user/100/ Hello, World!

    package main
    import (
        "fmt"
        "net/http"
    )
    func main() {
        http.HandleFunc("/user/100", func (w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "Welcome to my website!")
        })
       http.ListenAndServe(":80", nil)
    }
    
    

    $ curl http://localhost:80/user/100/ 404 page not found

Golanger Web Framework is a lightweight framework for writing web applications in Go.

/* Copyright 2013 Golanger.com. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except

Nov 14, 2022
The jin is a simplified version of the gin web framework that can help you quickly understand the core principles of a web framework.

jin About The jin is a simplified version of the gin web framework that can help you quickly understand the core principles of a web framework. If thi

Jul 14, 2022
⚡ Rux is an simple and fast web framework. support middleware, compatible http.Handler interface. 简单且快速的 Go web 框架,支持中间件,兼容 http.Handler 接口

Rux Simple and fast web framework for build golang HTTP applications. NOTICE: v1.3.x is not fully compatible with v1.2.x version Fast route match, sup

Dec 8, 2022
Roche is a Code Generator and Web Framework, makes web development super concise with Go, CleanArch
Roche is a Code Generator and Web Framework, makes web development super concise with Go, CleanArch

It is still under development, so please do not use it. We plan to release v.1.0.0 in the summer. roche is a web framework optimized for microservice

Sep 19, 2022
A powerful go web framework for highly scalable and resource efficient web application

webfr A powerful go web framework for highly scalable and resource efficient web application Installation: go get -u github.com/krishpranav/webfr Exa

Nov 28, 2021
A powerful go web framework for highly scalable and resource efficient web application

A powerful go web framework for highly scalable and resource efficient web application

Oct 3, 2022
A web app built using Go Buffalo web framework

Welcome to Buffalo Thank you for choosing Buffalo for your web development needs. Database Setup It looks like you chose to set up your application us

Feb 7, 2022
re:Web enables classic web applications to run on AWS Lambda.
re:Web enables classic web applications to run on AWS Lambda.

re:Web re:Web enables classic web applications to run on AWS Lambda. re:Web interfaces with the Lambda Runtime API. It translates API Gateway requests

Jan 1, 2023
Chainrand contract + web frontend + web backend

Chainrand-web This repo contains the implementation of Chainrand. https://chainrand.io Smart Contract Contains functionality to tie a Chainlink VRF to

Dec 8, 2021
Gin is a HTTP web framework written in Go (Golang).
Gin is a HTTP web framework written in Go (Golang).

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.

Jan 3, 2023
An ideally refined web framework for Go.

Air An ideally refined web framework for Go. High-performance? Fastest? Almost all web frameworks are using these words to tell people that they are t

Dec 15, 2022
An opinionated productive web framework that helps scaling business easier.
An opinionated productive web framework that helps scaling business easier.

appy An opinionated productive web framework that helps scaling business easier, i.e. focus on monolith first, only move to microservices with GRPC la

Nov 4, 2022
BANjO is a simple web framework written in Go (golang)

BANjO banjo it's a simple web framework for building simple web applications Install $ go get github.com/nsheremet/banjo Example Usage Simple Web App

Sep 27, 2022
beego is an open-source, high-performance web framework for the Go programming language.
beego is an open-source, high-performance web framework for the Go programming language.

Beego Beego is used for rapid development of enterprise application in Go, including RESTful APIs, web apps and backend services. It is inspired by To

Jan 1, 2023
High performance, minimalist Go web framework
High performance, minimalist Go web framework

Supported Go versions As of version 4.0.0, Echo is available as a Go module. Therefore a Go version capable of understanding /vN suffixed imports is r

Jan 2, 2023
⚡️ Express inspired web framework written in Go
⚡️ Express inspired web framework written in Go

Fiber is an Express inspired web framework built on top of Fasthttp, the fastest HTTP engine for Go. Designed to ease things up for fast development w

Jan 2, 2023
Gearbox :gear: is a web framework written in Go with a focus on high performance
Gearbox :gear: is a web framework written in Go with a focus on high performance

gearbox ⚙️ is a web framework for building micro services written in Go with a focus on high performance. It's built on fasthttp which is up to 10x fa

Jan 3, 2023
Goa is a web framework based on middleware, like koa.js.

Goa Goa is under construction, if you are familiar with koa or go and interested in this project, please join us. What is goa? goa = go + koa Just lik

Sep 27, 2022