ghost

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 18, 2022 License: MIT Imports: 24 Imported by: 0

README

drawing

Version Doc License

GHOST

A simple HTTP server framework for Go, bases on std net/http package.

Starts from 0.1.x, this framework should be used in go 1.18 or above, because it requires generics.

Usage

Just make an interesting ghost with all your businesses, then run it.

package main

import (
    "github.com/deadblue/ghost"
    "github.com/deadblue/ghost/view"
)

// YourGhost does all your businesses.
type YourGhost struct{}

// Get will handle request "GET /".
func (g *YourGhost) Get(_ ghost.Context) (ghost.View, error) {
    return view.Text("You are here!"), nil
}

// GetIndexAsHtml will handle request "GET /index.html".
func (g *YourGhost) GetIndexAsHtml(_ ghost.Context) (ghost.View, error) {
    return view.Text("index.html"), nil
}

// GetDataById will handle request "GET /data/{id}".
func (g *YourGhost) GetDataById(ctx ghost.Context) (ghost.View, error) {
	// Get "id" from path variable
    dataId := ctx.PathVar("id")
    return view.Text("Getting data whose id is " + dataId), nil
}

// PostUpdate will handle request "POST /update".
func (g *YourGhost) PostUpdate(ctx ghost.Context) (ghost.View, error) {
    // TODO: Get post data from ctx.Request()
    return view.Text("Update done!"), nil
}

// BuyMeACoffee will handle request "BUY /me/a/coffee"
func (g *YourGhost) BuyMeACoffee(_ ghost.Context) (ghost.View, error) {
    return view.Text("Thank you!"), nil
}

func main() {
	// Give me your ghost, I'll run it as an HTTP server.
    err := ghost.Born(&YourGhost{}).Run()
    if err != nil {
        panic(err)
    }
}

Mechanism

Each method on your ghost, whose signature is func(ghost.Context) (ghost.View, error), will be mounted as a request handler.

The method name, will be translated as the mount path, following these rules:

  • Suppose the method name is in camel-case, split it into words.
  • The first word will be treated as request method.
  • If there is no remain words, the method function will handle the root request.
  • If there are remained words, each word will be treated as a path segment.
  • In remain words, there are some special words that won't be treated as path segment:
    • By: The next word will be treated as a path variable.
    • As: Link the next word with "." instead of path separator ("/").

For examples:

Method Name Handle Request
Get GET /
GetIndex GET /index
GetIndexAsHtml GET /index.html
GetUserProfile GET /user/profile
PostUserProfile POST /user/profile
GetDataById GET /data/{id}
GetDataByTypeById GET /data/{type}/{id}
GetDataByIdAsJson GET /data/{id}.json
BuyMeACoffee BUY /me/a/coffee

Accessories

There are some optional interfaces, you can implement on your ghost.

  • StartupObserver
  • ShutdownObserver
  • ErrorHandler

Please check the reference for detail.

Limitations

Bases on the design and mechanism, GHOST has the following limitations:

  • GHOST can only handle the request in which each path segment is in lower case.
  • There can be only one path variable in each path segment.
  • You can not use GHOST to make a WebSocket server.

License

MIT

Documentation

Overview

Package ghost is a simple HTTP server framework.

Index

Constants

View Source
const (
	DefaultNetwork = "tcp"
	DefaultAddress = "127.0.0.1:9057"
)
View Source
const (
	Version = "0.1.2"
)

Variables

View Source
var (
	ErrNotFound = errors.New("not found")
)

Functions

func Implant added in v0.1.2

func Implant[G any](shell Shell, ghost G, root string) (err error)

Implant implants other ghosts into shell, and use "root" as prefix. "root" should not be empty and should start with "/".

NOTE: This function is not goroutine-safety, DO NOT call it in multiple goroutines.

Example:

shell := Born(&MasterGhost{})
Implant(shell, &FooGhost{}, "/foo")
Implant(shell, &BarGhost{}, "/bar")
shell.Run()

Q: Why is Implant not a method on Shell? A: Because Go DOES NOT allow using type parameter in method :(

Types

type Context

type Context interface {

	// Request returns the original HTTP request object.
	Request() *http.Request

	// Method returns request method.
	Method() string

	// Path returns request path.
	Path() string

	// Body returns a reader for request body.
	Body() io.Reader

	// Header returns request header with specific name.
	Header(name string) (string, bool)

	// Scheme returns request scheme(http/https).
	Scheme() string

	// Host returns request server host.
	Host() string

	// RemoteIp returns the client IP.
	RemoteIp() string

	// PathVar return the variable value in request path.
	PathVar(name string) string
}

Context wraps HTTP request, and provides some helpful methods.

type ErrorHandler added in v0.1.2

type ErrorHandler interface {

	// OnError will be called when HTTP 40x and 50x error occurred.
	OnError(ctx Context, err error) View
}

ErrorHandler is an optional interface for your ghost, which allows developer to customize the view when HTTP 40x and 50x error occurred.

type Shell

type Shell interface {

	// Startup starts up the shell manually, use this when you want to manage
	// shell's lifecycle by yourself. Otherwise, just use `Run`.
	Startup() error

	// Shutdown shuts down the shell manually, use this when you want to manage
	// shell's lifecycle by yourself. Otherwise, just use Run.
	Shutdown()

	// Done returns a read-only error channel, you will get error events from it
	// when the shell shut down, use this when you manage shell's lifecycle by
	// yourself. Otherwise, just use Run.
	Done() <-chan error

	// Run starts up the shell, and wait for it normally shut down or exit with
	// error.
	// Run will shut down shell when receives specific OS signals. If no signal
	// set, it will handle SIGINT and SIGTERM as default.
	Run(sig ...os.Signal) error
}

Shell is the shell of your ghost, it covers the basic reactions what an HTTP server should do, and dispatches requests to your ghost.

You can use Shell in two ways:

1. Simply run it:

// Create a shell from your ghost.
shell := ghost.Born(&YourGhost{})
// Just run the shell, Run will return when shell completely shut down.
if err := shell.Run(); err != nil {
	panic(err)
}

2. Manage its lifecycle by yourself:

// Create a shell from your ghost.
shell := ghost.Born(&YourGhost{})
// Start up the shell.
if err := shell.Startup(); err != nil {
	panic(err)
}
// Waiting for shell shut down.
for running := true; running; {
	select {
	case <- someEventArrived:
		// Shut down the shell.
		shell.Shutdown()
	case err := <- shell.Done():
		// The shell completely shut down.
		if err != nil {
			// Shut down with error
		} else {
			// Normally shut down.
		}
		// Exit the for-loop.
		running = false;
	}
}

func Born

func Born[Ghost any](ghost Ghost, options ...option.Option) Shell

Born builds a Shell for your ghost.

type ShutdownObserver added in v0.0.2

type ShutdownObserver interface {

	// AfterShutdown will always be called after Shell completely shut down, even
	// Shell shuts down with an error, developer can do finalizing works in it.
	// Currently, the returned error will be ignored.
	AfterShutdown() error
}

ShutdownObserver is an optional interface for your ghost.

type StartupObserver added in v0.0.2

type StartupObserver interface {

	// BeforeStartup will be called before Shell starts up, you can do some
	// initializing jos here. When BeforeStartup returns an error, the shell
	// won't start up, and return the error.
	BeforeStartup() error
}

StartupObserver allows developer be aware when the shell startup, developer can do initializing jobs at that time.

type View

type View interface {

	// Status returns response code.
	Status() int

	// Body returns an io.Reader for reading response body from, it will be
	// auto-closed after read if it implements io.Closer.
	Body() io.Reader
}

View describes the response.

type ViewHeaderInterceptor added in v0.0.2

type ViewHeaderInterceptor interface {

	// BeforeSendHeader will be called before kernel send the response headers to
	// client.
	// View can add/update/remove any headers in it.
	BeforeSendHeader(h http.Header)
}

ViewHeaderInterceptor is an optional interface for View. When a view implements it, kernel will pass response header to the view before send to client, view can manipulate the response header here.

type ViewSizeAdviser added in v0.0.2

type ViewSizeAdviser interface {

	// ContentLength returns the body size of the view, it will be set in response
	// header as "Content-Length", DO NOT return a incorrect value which is less or
	// more than the body size, that may cause some strange issues.
	ContentLength() int64
}

ViewSizeAdviser is an optional interface for View. Developer need not implement it when the view does not have a body, or the body is one of "bytes.Buffer", "bytes.Reader", "strings.Reader".

type ViewTypeAdviser added in v0.0.2

type ViewTypeAdviser interface {

	// ContentType returns the content type of the view, it will be set in response
	// header as "Content-Type".
	ContentType() string
}

ViewTypeAdviser is an optional interface for View. Developer need not implement it when the view does not have a body.

Directories

Path Synopsis
internal
Package view provides most common-use ghost.View implementations for developers.
Package view provides most common-use ghost.View implementations for developers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL