rest

package module
v0.0.0-...-33d5fe2 Latest Latest
Warning

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

Go to latest
Published: Jan 18, 2016 License: MIT Imports: 19 Imported by: 0

README

REST API

GoDoc Build Status Coverage Status

Библиотека для быстрого описания REST API.

Documentation

Overview

rest является простенькой библиотекой для быстрого создания web API проектов.

Основные ее достоинства в том, что она достаточно компактная, облегчает некоторые часто используемые вещи, поддерживает параметры в пути, совместима со стандартной библиотекой http и позволяет описывать обработчики в таком виде, как удобно мне.

Вообще, библиотека написана исключительно для внутреннего использования и нет никаких гарантий, что она не будет время от времени серьезно изменяться. Поэтому, если вы хотите использовать ее в своих проектах, то делайте fork и вперед.

Example
package main

import (
	"net/http"
	"os"

	"github.com/geotrace/rest"
)

func main() {
	var mux rest.ServeMux // инициализируем обработчик запросов
	// добавляем описание обработчиков, задавая пути, методы и функции их обработки
	mux.Handles(rest.Paths{
		// при задании путей можно использовать именованные параметры с ':'
		"/user/:id": {
			"GET": func(c *rest.Context) error {
				// можно быстро сформировать ответ в JSON
				return c.Send(rest.JSON{"user": c.Param("id")})
			},
			// для одного пути можно сразу задать все обработчики для разных методов
			"POST": func(c *rest.Context) error {
				var data = make(rest.JSON)
				// можно быстро десериализовать JSON, переданный в запросе, в объект
				if err := c.Bind(&data); err != nil {
					// возвращать ошибки тоже удобно
					return err
				}
				return c.Send(rest.JSON{"user": c.Param("id"), "data": data})
			},
		},
		// можно одновременно описать сразу несколько путей в одном месте
		"/message/:text": {
			"GET": func(c *rest.Context) error {
				// параметры пути получаются простым запросом
				return c.Send(rest.JSON{"message": c.Param("text")})
			},
		},
		"/file/:name": {
			"GET": func(c *rest.Context) error {
				// поддерживает отдачу разного типа данных, в том числе и файлов
				file, err := os.Open(c.Param("name") + ".html")
				if err != nil {
					return err
				}
				defer file.Close()
				// можно получать не только именованные элементы пути, но
				// параметры, используемые в запросе
				if c.Param("format") == "raw" {
					c.ContentType = `text/plain; charset="utf-8"`
				} else {
					c.ContentType = `text/html; charset="utf-8"`
				}
				return c.Send(file) // отдаем содержимое файла
			},
		},
		"/favicon.ico": {
			// для работы со статическими файлами определена специальная функция
			"GET": rest.File("./favicon.ico"),
		},
	})
	// можно сразу задать базовый путь для всех URL, используемых в обработчиках
	mux.BasePath = "/api/v1"
	// можно задать глобальные заголовки для всех ответов
	mux.Headers = map[string]string{
		"X-Powered-By": "My Server",
	}
	// т.к. поддерживается интерфейс http.Handler, то можно использовать
	// с любыми стандартными библиотеками http
	http.ListenAndServe(":8080", mux)
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// Взведенный флаг Debug указывает, что описания ошибок возвращаются как
	// есть. В противном случае всегда возвращается только стандартное описание
	// статуса HTTP, сформированное на базе этой ошибки.
	Debug bool = false
	// Флаг Compress разрешает сжатие данных. Чтобы запретить сжимать данные,
	// установите значение данного флага в false. При инициализации сжатия
	// проверяется, что оно уже не включено, например, на уровне глобального
	// обработчика запросов и, в этом случае, сжатие не будет включено, даже
	// если флаг установлен.
	Compress bool = true
	// Encoder описывает функции, используемые для разбора запроса и кодирования
	// ответа. MaxBody задает максимальный размер поддерживаемого запроса.
	// Если размер превышает указанный, то возвращается ошибка. Если не хочется
	// ограничений, то можно установить значение 0, тогда проверка производиться
	// не будет.
	Encoder Coder = NewJSONCoder(1 << 15) // 32 мегабайта
	// EncodeError управляет форматом вывода ошибок: если флаг не взведен, то
	// ошибки отдаются как текст. В противном случае описание ошибок
	// кодируются с помощью Encoder и содержат статус и описание ошибки.
	EncodeError bool = true
)
View Source
var (
	ErrDataAlreadySent       = errors.New("data already sent")
	ErrBadRequest            = errors.New("bad request")              // 400
	ErrUnauthorized          = errors.New("unauthorized")             // 401
	ErrForbidden             = errors.New("forbidden")                // 403
	ErrNotFound              = errors.New("not found")                // 404
	ErrLengthRequired        = errors.New("length required")          // 411
	ErrRequestEntityTooLarge = errors.New("request entity too large") // 413
	ErrUnsupportedMediaType  = errors.New("unsupported media type")   // 415
	ErrInternalServerError   = errors.New("internal server error")    // 500
	ErrNotImplemented        = errors.New("not implemented")          // 501
	ErrServiceUnavailable    = errors.New("service unavailable")      // 503
)

Эти ошибки обрабатываются при передаче их в метод Context.Send и устанавливают соответствующий статус ответа.

Кроме указанных здесь ошибок, так же проверяется, что ошибка отвечает на os.IsNotExist (в этом случае статус станет 404) или os.IsPermission (статус 403). Все остальные ошибки устанавливают статус 500.

Functions

func SetLogger

func SetLogger(out io.Writer)

SetLogger позволяет определить вывод лога обработки запросов и ошибок. Если установлен флаг Debug, то в лог так же пишутся все запросы, которые вызвали ошибку и дамп вызовов функций, приведших к panic.

Types

type Coder

type Coder interface {
	Bind(*Context, interface{}) error
	Encode(*Context, interface{}) error
}

Coder описывает интерфейс для поддержки разбора запроса и кодирования ответа.

type Context

type Context struct {
	*http.Request        // HTTP запрос в разобранном виде
	ContentType   string // тип информации в ответе
	// contains filtered or unexported fields
}

Context содержит контекстную информацию HTTP-запроса и методы формирования ответа на них. Т.к. http.Request импортируется в Context напрямую, то можно использовать все его свойства и методы, как родные свойства и методы самого контекста.

Context скрывает http.ResponseWriter от прямого использования и, вместо этого, предоставляет свои методы для формирования ответа. Это позволяет обойти некоторые скользкие моменты и, иногда, несколько упростить код.

При отдаче ответа анализируются первые байты данных и на основании них устанавливается тип ответа. Если вы хотите определить тип ответа самостоятельно, то проще всего установить значение ContentType строкой с описанием нужного типа.

Есть два пути отослать в ответ ошибку: послать ее через Context.Send или вернуть ее из обработчика. Результат, в конечном счете, будет приблизительно одинаковый: разница только в том, что при возврате ошибки из обработчика, она будет записана в лог, а в случае посылки ее через Send — нет. Чтобы не путаться и не выбирать наилучший способ, просто рассматривайте возврат ошибки из обработчика, как мягкий вариант паники (panic).

func (*Context) Bind

func (c *Context) Bind(obj interface{}) error

Bind разбирает данные запроса и заполняет ими указанный в параметре объект. Разбор осуществляется с помощью Encoder.

Example
package main

import (
	"github.com/geotrace/rest"
)

var c = new(rest.Context)

func main() error {
	// инициализируем формат данных для разбора
	data := make(map[string]interface{})
	// читаем запрос и получаем данные в разобранном виде
	if err := c.Bind(&data); err != nil {
		return err
	}
	// возвращаем эти же данные в ответ
	return c.Send(data)
}
Output:

func (*Context) Data

func (c *Context) Data(key interface{}) interface{}

Data возвращает пользовательские данные, сохраненные в контексте запроса с указанным ключем. Обычно эти данные используются, когда необходимо передать их между несколькими обработчиками.

func (*Context) Error

func (c *Context) Error(code int, msg string) error

Error отправляет указанный текст как описание ошибки. В зависимости от флага EncodeError, данный текст будет отдан как описание или как JSON с кодом статуса. В отличии от обычных ошибок, на данный текст не распространяется правило отладки и текст будет отдан в неизменном виде, в не зависимости от установленного значения Debug.

func (*Context) Flush

func (c *Context) Flush()

Flush отдает накопленный буфер с ответом. Используется для поддержки интерфейса http.Flusher.

func (*Context) Header

func (c *Context) Header() http.Header

Header возвращает HTTP-заголовки ответа. Используется для поддержки интерфейса http.ResponseWriter.

func (*Context) Param

func (c *Context) Param(key string) string

Param возвращает значение именованного параметра. Если параметр с таким именем не найден, то возвращается значение параметра из URL с тем же именем.

Разобранные параметры запроса пути сохраняются внутри Context и повторного его разбора уже не требует. Но это происходит только при первом к ним обращении.

func (*Context) Redirect

func (c *Context) Redirect(url string) error

Redirect отсылает ответ с требованием временного перехода по указанному URL. Ошибка никогда не возвращается.

func (*Context) Send

func (c *Context) Send(data interface{}) (err error)

Send отсылает переданные данные как ответ на запрос. В зависимости от типа данных, используются разные форматы ответов. Поддерживаются данные в формате string, error, []byte, io.Reader и nil. Все остальные типы данных приводятся к формату JSON.

Данный метод можно использовать только один раз: после того, как ответ отправлен, повторный вызов данного метода сразу возвращает ошибку.

Example (File)
package main

import (
	"os"

	"github.com/geotrace/rest"
)

var c = new(rest.Context)

func main() error {
	// открываем файл
	file, err := os.Open("README.md")
	if err != nil {
		return err
	}
	defer file.Close()
	// устанавливаем тип отдаваемых данных
	c.ContentType = "text/markdown; charset=UTF-8"
	// отдаем содержимое файла в качестве ответа
	return c.Send(file)
}
Output:

Example (Json)
package main

import (
	"github.com/geotrace/rest"
)

var c = new(rest.Context)

func main() error {
	// отдаем ответ в формате JSON, беря идентификатор пользователя
	// из параметров пути или запроса
	return c.Send(rest.JSON{"user": c.Param("id")})
}
Output:

func (*Context) SetData

func (c *Context) SetData(key, value interface{})

SetData сохраняет пользовательские данные в контексте запроса с указанным ключем.

Рекомендуется в качестве ключа использовать какой-нибудь приватный тип и его значение, чтобы избежать случайного затирания данных другими обработчиками: это гарантированно обезопасит от случайного доступа к ним. Но строки тоже поддерживаются. :)

Example
package main

import (
	"fmt"

	"github.com/geotrace/rest"
)

var c = new(rest.Context)

func main() {
	type myKeyType byte     // определяем собственный тип данных
	var myKey myKeyType = 1 // генерируем уникальный ключ данных
	// сохраняем данные в контексте, используя уникальный ключ
	c.SetData(myKey, "Test data")
	// читаем данные с помощью ключа
	str := c.Data(myKey).(string)
	fmt.Println(str)
}
Output:

func (*Context) Status

func (c *Context) Status(code int) *Context

Status устанавливает код HTTP-ответа, который будет отправлен сервером. Вызов данного метода не приводит к немедленной отправке ответа, а только устанавливает внутренний статус. Статус должен быть в диапазоне от 200 до 599, в противном случае статус не изменяется.

Метод возвращает ссылку на основной контекст, чтобы можно было использовать его в последовательности выполнения команд. Например, можно сразу установить код ответа и тут же опубликовать данные.

Example
package main

import (
	"github.com/geotrace/rest"
)

var c = new(rest.Context)

func main() error {
	// возвращаем 201 код окончания
	return c.Status(201).Send(nil)
}
Output:

func (*Context) Write

func (c *Context) Write(data []byte) (int, error)

Write записывает данные в качестве ответа сервера. Может вызываться несколько раз. Используется для поддержки интерфейса http.ResponseWriter.

При первом вызове (может быть не явный) автоматически устанавливается статус ответа. Если статус ответа был не задан, то будет использован статус 200 (ОК). Так же, если не был задан ContentType, то он будет определен автоматически на основании анализа первых байт данных.

func (*Context) WriteHeader

func (c *Context) WriteHeader(code int)

WriteHeader записывает заголовок ответа. Вызов метода автоматически взводит внутренний флаг, что отправка ответа начата. После его вызова отсылка каких-либо данных другим способом, кроме Write, уже не поддерживается. Используется для поддержки интерфейса http.ResponseWriter.

type Handler

type Handler func(*Context) error

Handler может являться любая функция, которая принимает Context и может возвращать ошибку. Возвращаемая ошибка может быть записана в лог и, если сервер еще не отсылал никакого ответа, то будет возвращена вместе с ошибкой в качестве ответа.

func Data

func Data(data interface{}, contentType string) Handler

Data постоянно отдает указанные в параметрах данные в виде ответа на запрос.

Example
package main

import (
	"github.com/geotrace/rest"
)

var mux rest.ServeMux

func main() {
	mux.Handle("GET", "/static/", rest.Data("OK", ""))
	mux.Handle("GET", "/bin/",
		rest.Data([]byte{0x1, 0x2, 0x3, 0x4}, "application/octet-stream"))
}
Output:

func File

func File(name string) Handler

File отдает на запрос содержимое файла с указанным именем.

Example
package main

import (
	"github.com/geotrace/rest"
)

var mux rest.ServeMux

func main() {
	mux.Handle("GET", "/favicon.ico", rest.File("./favicon.ico"))
}
Output:

func Files

func Files(dir string) Handler

Files отдает файлы по имени из указанного каталога. Имя файла задается в пути в виде последнего именованного параметра.

Example
package main

import (
	"github.com/geotrace/rest"
)

var mux rest.ServeMux

func main() {
	mux.Handle("GET", "/files/:name", rest.Files("./tmp/"))
}
Output:

func Handlers

func Handlers(handlers ...Handler) Handler

Handlers объединяет несколько обработчиков запросов в очередь. Они будут выполняться в той последовательности, в которой были добавлены одна за другой, пока не будут выполнены все или пока не вернется первая ошибка, которая прерывает процес дальнейшей обработки. С помощью этой функции можно объединять несколько обработчиков в один.

func Redirect

func Redirect(url string) Handler

Redirect возвращает Handler, который осуществляет постоянное перенаправление на указанный в параметрах URL.

Example
package main

import (
	"github.com/geotrace/rest"
)

var mux rest.ServeMux

func main() {
	mux.Handle("GET", "/redirect/", rest.Redirect("/json/"))
}
Output:

func (Handler) ServeHTTP

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP поддерживает интерфейс http.Handler для Handler, что позволяет использовать его с любыми совместимыми с http.Handler библиотеками.

Example
package main

import (
	"net/http"
	"time"

	"github.com/geotrace/rest"
)

func main() {
	http.ListenAndServe(":8080",
		rest.Handler(func(c *rest.Context) error {
			return c.Send(rest.JSON{
				"user": "name",
				"date": time.Now().UTC(),
			})
		}))
}
Output:

type JSON

type JSON map[string]interface{}

JSON позволяет быстро описать данные в одноименном формате.

type JSONCoder

type JSONCoder struct {
	MaxBody int64 // максимально допустимый размер запроса
}

JSONCoder осуществляет разбор запроса и кодирование ответа в формате JSON.

func NewJSONCoder

func NewJSONCoder(maxSize int64) *JSONCoder

NewJSONCoder возвращает новый инициализированный Coder, поддерживающий формат JSON.

func (JSONCoder) Bind

func (j JSONCoder) Bind(c *Context, obj interface{}) error

Bind разбирает данные запроса в формате JSON и заполняет ими указанный в параметре объект.

Если Content-Type запроса не соответствует "application/json", то возвращается ошибка ErrUnsupportedMediaType. Так же может возвращать ошибку ErrLengthRequired, если не указана длина запроса, ErrRequestEntityTooLarge — если запрос превышает значение MaxBody, и ErrBadRequest — если не смогли разобрать запрос и поместить результат разбора в объект obj. Все эти ошибки поддерживаются методом Send и отдают соответствующий статус ответа на запрос.

func (JSONCoder) Encode

func (JSONCoder) Encode(c *Context, obj interface{}) error

Encode кодирует и отправляет ответ с содержимым obj в формате JSON.

type Methods

type Methods map[string]Handler

Methods позволяет описать обработчики для методов.

type Paths

type Paths map[string]Methods

Paths позволяет описать сразу несколько обработчиков для разных путей и методов: ключем для данного словаря как раз являются пути запросов. Используется в качестве аргумента при вызове метода ServeMux.Handles.

type ServeMux

type ServeMux struct {
	// Позволяет задать базовый путь для всех запросов.
	BasePath string
	// Описывает дополнительные заголовки HTTP-ответа, которые будут добавлены
	// ко всем ответам, возвращаемым данным обработчиком
	Headers map[string]string
	// contains filtered or unexported fields
}

ServeMux описывает список обработчиков, ассоциированных с путями запроса и методами.

func (*ServeMux) Handle

func (m *ServeMux) Handle(method, path string, handler Handler)

Handle регистрирует обработчик для указанного метода и пути. В описании пути можно использовать именованные параметры (начинаются с символа ':') и завершающий именованный параметр (начинается с '*'), который указывает, что path может быть длиннее. В последнем случае вся остальная часть пути будет включена в данный параметр. Параметр со звездочкой, если указан, должен быть самым последним параметром пути.

Если количество элементов пути в path больше 32768 или параметр со звездочкой используется не в самом последнем элементе пути, то возникает panic.

Example
package main

import (
	"time"

	"github.com/geotrace/rest"
)

var mux rest.ServeMux

func main() {
	mux.Handle("GET", "/json/",
		func(c *rest.Context) error {
			return c.Send(rest.JSON{
				"user": "name",
				"date": time.Now().UTC(),
			})
		})
}
Output:

func (ServeMux) Handler

func (m ServeMux) Handler(c *Context) error

Handler отвечает за подбор обработчика и его выполнение.

Если обработчик для данного пути и метода не найден, но есть обработчики для других методов, то возвращается статус http.StatusMethodNotAllowed и в заголовке передается список методов, которые можно применить к данному пути. В противном случае возвращается статус http.StatusNotFound.

func (*ServeMux) Handles

func (m *ServeMux) Handles(paths Paths)

Handles добавляет сразу список обработчиков для нескольких путей и методов. Это, по сути, просто удобный способ сразу определить большое количество обработчиков, не вызывая каждый раз ServeMux.Handle.

Example
package main

import (
	"net/http"

	"github.com/geotrace/rest"
)

type User struct{}

func (User) get(*rest.Context) error     { return nil }
func (User) post(*rest.Context) error    { return nil }
func secure(h rest.Handler) rest.Handler { return h }

var (
	user       User
	getMessage = func(*rest.Context) error { return nil }
	getFile    = getMessage
)

func main() {
	var mux rest.ServeMux
	mux.Handles(rest.Paths{
		"/user/:id": {
			"GET":  user.get,
			"POST": user.post,
		},
		"/message/:text": {"GET": getMessage},
		"/file/:name":    {"GET": secure(getFile)},
	})
	// т.к. поддерживается интерфейс http.Handler, то можно использовать
	// с любыми стандартными библиотеками
	http.ListenAndServe(":8080", mux)
}
Output:

func (ServeMux) ServeHTTP

func (m ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP обеспечивает поддержку интерфейса http.Handler. Таким образом, данный ServeMux можно использовать как стандартный обработчик HTTP-запросов.

В процессе обработки запроса отслеживаются возвращаемые ошибки и перехватываются возможные вызовы panic. Если ответ на запрос еще не отправлялся, то в этих случаях в ответ будет отправлена ошибка.

Example
package main

import (
	"net/http"
	"time"

	"github.com/geotrace/rest"
)

var mux rest.ServeMux

func main() {
	mux.Handles(rest.Paths{
		"/user/:id": {
			"GET": func(c *rest.Context) error {
				return c.Send(rest.JSON{
					"user": c.Param("id"),
					"date": time.Now().UTC(),
				})
			},
			"POST": rest.Data("OK", "text/plain"),
		},
		"/favicon.ico": {
			"GET": rest.File("./favicon.ico"),
		},
	})
	http.ListenAndServe(":8080", mux)
}
Output:

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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