API is subject to change until v1.0
Version | Supported Gemini version |
---|---|
0.9.4 | v0.14.* |
< 0.9.4 | v0.13.* |
x509.Certificate
directly from context)package main
import "github.com/pitr/gig"
func main() {
// Gig instance
g := gig.Default()
// Routes
g.Handle("/", func(c gig.Context) error {
return c.Gemini("# Hello, World!")
})
// Start server on PORT or default port
g.Run("my.crt", "my.key")
}
$ go run main.go
package main
import "github.com/pitr/gig"
func main() {
g := gig.Default()
g.Handle("/user/:name", func(c gig.Context) error {
return c.Gemini("# Hello, %s!", c.Param("name"))
})
g.Run("my.crt", "my.key")
}
package main
import "github.com/pitr/gig"
func main() {
g := gig.Default()
g.Handle("/user", func(c gig.Context) error {
query, err := c.QueryString()
if err != nil {
return err
}
return c.Gemini("# Hello, %s!", query)
})
g.Run("my.crt", "my.key")
}
package main
import "github.com/pitr/gig"
func main() {
g := gig.Default()
g.Handle("/user", func(c gig.Context) error {
cert := c.Certificate()
if cert == nil {
return c.NoContent(gig.StatusClientCertificateRequired, "We need a certificate")
}
return c.Gemini("# Hello, %s!", cert.Subject.CommonName)
})
// OR using middleware
g.Handle("/user", func(c gig.Context) error {
return c.Gemini("# Hello, %s!", c.Get("subject"))
}, gig.CertAuth(gig.ValidateHasCertificate))
g.Run("my.crt", "my.key")
}
func main() {
g := gig.Default()
// Simple group: v1
v1 := g.Group("/v1")
{
v1.Handle("/page1", page1Endpoint)
v1.Handle("/page2", page2Endpoint)
}
// Simple group: v2
v2 := g.Group("/v2")
{
v2.Handle("/page1", page1Endpoint)
v2.Handle("/page2", page2Endpoint)
}
g.Run("my.crt", "my.key")
}
Use
g := gig.New()
instead of
// Default With the Logger and Recovery middleware already attached
g := gig.Default()
func main() {
// Creates a router without any middleware by default
g := gig.New()
// Global middleware
// Logger middleware will write the logs to gig.DefaultWriter.
// By default gig.DefaultWriter = os.Stdout
g.Use(gig.Logger())
// Recovery middleware recovers from any panics and return StatusPermanentFailure.
g.Use(gig.Recovery())
// Private group
// same as private := g.Group("/private", gig.CertAuth(gig.ValidateHasCertificate))
private := g.Group("/private")
private.Use(gig.CertAuth(gig.ValidateHasCertificate))
{
private.Handle("/user", userEndpoint)
}
g.Run("my.crt", "my.key")
}
func main() {
f, _ := os.Create("access.log")
gig.DefaultWriter = io.MultiWriter(f)
// Use the following code if you need to write the logs to file and console at the same time.
// gig.DefaultWriter = io.MultiWriter(f, os.Stdout)
g := gig.Default()
g.Handle("/", func(c gig.Context) error {
return c.Gemini("# Hello, World!")
})
g.Run("my.crt", "my.key")
}
func main() {
g := gig.New()
// See LoggerConfig documentation for format
g.Use(gig.LoggerWithConfig(gig.LoggerConfig{Format: "${remote_ip} ${status}"}))
g.Handle("/", func(c gig.Context) error {
return c.Gemini("# Hello, World!")
})
g.Run("my.crt", "my.key")
}
func main() {
g := gig.Default()
// Register /images/* to serve files in my_images/ folder.
// Requests to /images/ will show directory listing.
g.Static("/images", "my_images")
g.File("/robots.txt", "files/robots.txt")
g.Run("my.crt", "my.key")
}
func main() {
g := gig.Default()
g.Handle("/robots.txt", func(c gig.Context) error {
return c.File("robots.txt")
})
g.Run("my.crt", "my.key")
}
func main() {
g := gig.Default()
g.Handle("/data", func(c gig.Context) error {
response, err := http.Get("https://google.com/")
if err != nil || response.StatusCode != http.StatusOK {
return c.NoContent(gig.StatusProxyError, "could not fetch google")
}
return c.Stream("text/html", response.Body)
})
g.Run("my.crt", "my.key")
}
Set Gig.Renderer
to something that responds to Render(io.Writer, string, interface{}, gig.Context) error
.
Use any templating library, such as text/template
, https://github.com/valyala/quicktemplate, etc. The following example uses text/template
:
import (
"text/template"
"github.com/pitr/gig"
)
type Template struct {
templates *template.Template
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c gig.Context) error {
// Execute named template with data
return t.templates.ExecuteTemplate(w, name, data)
}
func main() {
g := gig.Default()
// Register renderer
g.Renderer = &Template{template.Must(template.ParseGlob("public/views/*.gmi"))}
g.Handle("/user/:name", func(c gig.Context) error {
// Render template "user" with username passed as data.
return c.Render("user", c.Param("name"))
})
g.Run("my.crt", "my.key")
}
Consider bundling assets with the binary by using go:ember, go-assets or similar.
func main() {
g := gig.Default()
g.Handle("/old", func(c gig.Context) error {
return c.NoContent(gig.StatusRedirectPermanent, "/new")
})
g.Run("my.crt", "my.key")
}
func main() {
apps := map[string]*gig.Gig{}
// App A
a := gig.Default()
apps["app-a.example.com"] = a
a.Handle("/", func(c gig.Context) error {
return c.Gemini("I am App A")
})
// App B
b := gig.Default()
apps["app-b.example.com"] = b
b.Handle("/", func(c gig.Context) error {
return c.Gemini("I am App B")
})
// Server (without default middleware to prevent double logging)
g := gig.New()
g.Handle("/*", func(c gig.Context) error {
app := apps[c.URL().Host]
if app == nil {
return gig.ErrNotFound
}
app.ServeGemini(c)
return nil
})
g.Run("my.crt", "my.key") // must be wildcard SSL certificate for *.example.com
}
Status: EXPERIMENTAL
PassAuth
middleware ensures that request has a client certificate, validates its fingerprint using function passed to middleware. If authentication is required, this function should return a path where user should be redirect to.
Login handlers are setup using PassAuthLoginHandle
function, which collects username and password, and passes them to the provided function. That function should return an error if login failed, or absolute path to redirect user to.
User registration is expected to be implemented by developer.
The example assumes that there is a db
module that does user management.
func main() {
g := gig.Default()
secret := g.Group("/secret", gig.PassAuth(func(sig string, c gig.Context) (string, error) {
ok, err := db.CheckValid(sig)
if err != nil {
return "/login", err
}
if !ok {
return "/login", nil
}
return "", nil
}))
// secret.Handle("/page", func(c gig.Context) {...})
g.PassAuthLoginHandle("/login", func(user, pass, sig string, c Context) (string, error) {
// check user/pass combo, and activate cert signature if valid
err := db.Login(user, pass, sig)
if err != nil {
return "", err
}
return "/secret/page", nil
})
g.Run("my.crt", "my.key")
}
func MyMiddleware(next gig.HandlerFunc) gig.HandlerFunc {
return func(c gig.Context) error {
// Set example variable
c.Set("example", "123")
if err := next(c); err != nil {
c.Error(err)
}
// Do something after request is done
// ...
return err
}
}
func main() {
g := gig.Default()
g.Use(MyMiddleware)
g.Handle("/", func(c gig.Context) error {
return c.Gemini("# Example %s", c.Get("example"))
})
g.Run("my.crt", "my.key")
}
Use PORT
environment variable:
PORT=12345 ./myapp
Alternatively, pass it to Run:
func main() {
g := gig.Default()
g.Handle("/", func(c gig.Context) error {
return c.Gemini("# Hello world")
})
g.Run(":12345", "my.crt", "my.key")
}
func main() {
g := gig.Default()
g.TLSConfig.MinVersion = tls.VersionTLS13
g.Handle("/", func(c gig.Context) error {
return c.Gemini("# Hello world")
})
g.Run("my.crt", "my.key")
}
func setupServer() *gig.Gig {
g := gig.Default()
g.Handle("/private", func(c gig.Context) error {
return c.Gemini("Hello %s", c.Get("subject"))
}, gig.CertAuth(gig.ValidateHasCertificate))
return g
}
func TestServer(t *testing.T) {
g := setupServer()
c, res := g.NewFakeContext("/private", nil)
g.ServeGemini(c)
if res.Written != "60 Client Certificate Required\r\n" {
t.Fail()
}
}
func TestCertificate(t *testing.T) {
g := setupServer()
c, res := g.NewFakeContext("/", &tls.ConnectionState{
PeerCertificates: []*x509.Certificate{
{Subject: pkix.Name{CommonName: "john"}},
},
})
g.ServeGemini(c)
if resp.Written != "20 text/gemini\r\nHello john" {
t.Fail()
}
}
Gig is used by the following capsules:
If you use Gig, open a PR to add your capsule to this list.
Benchmark name | (1) | (2) | (3) | (4) |
---|---|---|---|---|
BenchmarkRouterStaticRoutes | 104677 | 11105 ns/op | 0 B/op | 0 allocs/op |
BenchmarkRouterGitHubAPI | 50859 | 22973 ns/op | 0 B/op | 0 allocs/op |
BenchmarkRouterParseAPI | 302828 | 3717 ns/op | 0 B/op | 0 allocs/op |
BenchmarkRouterGooglePlusAPI | 185558 | 6136 ns/op | 0 B/op | 0 allocs/op |
Generated using make bench
in router_test.go. APIs are based on Go HTTP Router Benchmark repository and adapted to Gemini protocol, eg. verbs
GET/POST/etc are ignored since Gemini does not support them.
If something is missing, please open an issue. If possible, send a PR.