Package gin implements a HTTP web framework called gin.
See https://gin-gonic.com/ for more information about gin.
Content-Type MIME of the most common data formats.
View Sourceconst ( PlatformGoogleAppEngine = "X-Appengine-Remote-Addr" PlatformCloudflare = "CF-Connecting-IP" PlatformFlyIO = "Fly-Client-IP" )
Trusted platforms
View Sourceconst ( DebugMode = "debug" ReleaseMode = "release" TestMode = "test" )
AuthProxyUserKey is the cookie name for proxy_user credential in basic auth for proxy.
AuthUserKey is the cookie name for user credential in basic auth.
BindKey indicates a default bind key.
View Sourceconst BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
BodyBytesKey indicates a default body bytes key.
View Sourceconst ContextKey = "_gin-gonic/gin/contextkey"
ContextKey is the key that a Context returns itself for.
EnvGinMode indicates environment name for gin mode.
Version is the current gin framework's version.
DebugPrintFunc indicates debug log output format.
View Sourcevar DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
DebugPrintRouteFunc indicates debug log output format.
DefaultErrorWriter is the default io.Writer used by Gin to debug errors
DefaultWriter is the default io.Writer used by Gin for debug output and middleware output like Logger() or Recovery(). Note that both Logger and Recovery provides custom ways to configure their output io.Writer. To support coloring in Windows use:
import "github.com/mattn/go-colorable" gin.DefaultWriter = colorable.NewColorableStdout()
CreateTestContext returns a fresh engine and context for testing purposes
Dir returns a http.FileSystem that can be used by http.FileServer(). It is used internally in router.Static(). if listDirectory == true, then it works the same as http.Dir() otherwise it returns a filesystem that prevents http.FileServer() to list the directory files.
func DisableBindValidation()
DisableBindValidation closes the default validator.
func DisableConsoleColor()
DisableConsoleColor disables color output in the console.
func EnableJsonDecoderDisallowUnknownFields()
EnableJsonDecoderDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to call the DisallowUnknownFields method on the JSON Decoder instance.
func EnableJsonDecoderUseNumber()
EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumber to call the UseNumber method on the JSON Decoder instance.
ForceConsoleColor force color output in the console.
IsDebugging returns true if the framework is running in debug mode. Use SetMode(gin.ReleaseMode) to disable debug mode.
Mode returns current gin mode.
SetMode sets gin mode according to input string.
Accounts defines a key/value for user/pass list of authorized logins.
Context is the most important part of gin. It allows us to pass variables between middleware, manage the flow, validate the JSON of a request and render a JSON response for example.
CreateTestContextOnly returns a fresh context base on the engine for testing purposes
Abort prevents pending handlers from being called. Note that this will not stop the current handler. Let's say you have an authorization middleware that validates that the current request is authorized. If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers for this request are not called.
AbortWithError calls `AbortWithStatus()` and `Error()` internally. This method stops the chain, writes the status code and pushes the specified error to `c.Errors`. See Context.Error() for more details.
AbortWithStatus calls `Abort()` and writes the headers with the specified status code. For example, a failed attempt to authenticate a request could use: context.AbortWithStatus(401).
AbortWithStatusJSON calls `Abort()` and then `JSON` internally. This method stops the chain, writes the status code and return a JSON body. It also sets the Content-Type as "application/json".
AddParam adds param to context and replaces path param key with given value for e2e testing purposes Example Route: "/user/:id" AddParam("id", 1) Result: "/user/1"
AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string. It also sets the Content-Type as "application/json".
Bind checks the Method and Content-Type to select a binding engine automatically, Depending on the "Content-Type" header different bindings are used, for example:
"application/json" --> JSON binding "application/xml" --> XML binding
It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer. It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
BindUri binds the passed struct pointer using binding.Uri. It will abort the request with HTTP 400 if any error occurs.
BindWith binds the passed struct pointer using the specified binding engine. See the binding package.
Deprecated: Use MustBindWith or ShouldBindWith.
BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
ClientIP implements one best effort algorithm to return the real client IP. It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy, the remote IP (coming from Request.RemoteAddr) is returned.
ContentType returns the Content-Type header of the request.
Cookie returns the named cookie provided in the request or ErrNoCookie if not found. And return the named cookie is unescaped. If multiple cookies match the given name, only one cookie will be returned.
Copy returns a copy of the current context that can be safely used outside the request's scope. This has to be used when the context has to be passed to a goroutine.
Data writes some data into the body stream and updates the HTTP code.
DataFromReader writes the specified reader into the body stream and updates the HTTP code.
Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
DefaultPostForm returns the specified key from a POST urlencoded form or multipart form when it exists, otherwise it returns the specified defaultValue string. See: PostForm() and GetPostForm() for further information.
DefaultQuery returns the keyed url query value if it exists, otherwise it returns the specified defaultValue string. See: Query() and GetQuery() for further information.
GET /?name=Manu&lastname= c.DefaultQuery("name", "unknown") == "Manu" c.DefaultQuery("id", "none") == "none" c.DefaultQuery("lastname", "none") == ""
func (c *Context) Done() <-chan struct{}
Done returns nil (chan which will wait forever) when c.Request has no Context.
Err returns nil when c.Request has no Context.
Error attaches an error to the current context. The error is pushed to a list of errors. It's a good idea to call Error for each error that occurred during the resolution of a request. A middleware can be used to collect all the errors and push them to a database together, print a log, or append it in the HTTP response. Error will panic if err is nil.
File writes the specified file into the body stream in an efficient way.
FileAttachment writes the specified file into the body stream in an efficient way On the client side, the file will typically be downloaded with the given filename
FileFromFS writes the specified file from http.FileSystem into the body stream in an efficient way.
FormFile returns the first file for the provided form key.
FullPath returns a matched route full path. For not found routes returns an empty string.
router.GET("/user/:id", func(c *gin.Context) { c.FullPath() == "/user/:id" // true })
Get returns the value for the given key, ie: (value, true). If the value does not exist it returns (nil, false)
GetBool returns the value associated with the key as a boolean.
GetDuration returns the value associated with the key as a duration.
GetFloat64 returns the value associated with the key as a float64.
GetHeader returns value from request headers.
GetInt returns the value associated with the key as an integer.
GetInt64 returns the value associated with the key as an integer.
GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded form or multipart form when it exists `(value, true)` (even when the value is an empty string), otherwise it returns ("", false). For example, during a PATCH request to update the user's email:
email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com" email= --> ("", true) := GetPostForm("email") // set email to "" --> ("", false) := GetPostForm("email") // do nothing with email
GetPostFormArray returns a slice of strings for a given form key, plus a boolean value whether at least one value exists for the given key.
GetPostFormMap returns a map for a given form key, plus a boolean value whether at least one value exists for the given key.
GetQuery is like Query(), it returns the keyed url query value if it exists `(value, true)` (even when the value is an empty string), otherwise it returns `("", false)`. It is shortcut for `c.Request.URL.Query().Get(key)`
GET /?name=Manu&lastname= ("Manu", true) == c.GetQuery("name") ("", false) == c.GetQuery("id") ("", true) == c.GetQuery("lastname")
GetQueryArray returns a slice of strings for a given query key, plus a boolean value whether at least one value exists for the given key.
GetQueryMap returns a map for a given query key, plus a boolean value whether at least one value exists for the given key.
GetRawData returns stream data.
GetString returns the value associated with the key as a string.
GetStringMap returns the value associated with the key as a map of interfaces.
GetStringMapString returns the value associated with the key as a map of strings.
GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
GetStringSlice returns the value associated with the key as a slice of strings.
GetTime returns the value associated with the key as time.
GetUint returns the value associated with the key as an unsigned integer.
GetUint64 returns the value associated with the key as an unsigned integer.
func (*Context) Handler ¶ added in v1.3.0Handler returns the main handler.
func (*Context) HandlerName ¶HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", this function will return "main.handleGetUsers".
func (*Context) HandlerNames ¶ added in v1.4.0HandlerNames returns a list of all registered handlers for this context in descending order, following the semantics of HandlerName()
Header is an intelligent shortcut for c.Writer.Header().Set(key, value). It writes a header in the response. If value == "", this method removes the header `c.Writer.Header().Del(key)`
IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body. It also sets the Content-Type as "application/json". WARNING: we recommend using this only for development purposes since printing pretty JSON is more CPU and bandwidth consuming. Use Context.JSON() instead.
IsAborted returns true if the current context was aborted.
IsWebsocket returns true if the request headers indicate that a websocket handshake is being initiated by the client.
JSON serializes the given struct as JSON into the response body. It also sets the Content-Type as "application/json".
JSONP serializes the given struct as JSON into the response body. It adds padding to response body to request data from a server residing in a different domain than the client. It also sets the Content-Type as "application/javascript".
MultipartForm is the parsed multipart form, including file uploads.
MustBindWith binds the passed struct pointer using the specified binding engine. It will abort the request with HTTP 400 if any error occurs. See the binding package.
MustGet returns the value for the given key if it exists, otherwise it panics.
Negotiate calls different Render according to acceptable Accept format.
NegotiateFormat returns an acceptable Accept format.
Next should be used only inside middleware. It executes the pending handlers in the chain inside the calling handler. See example in GitHub.
Param returns the value of the URL param. It is a shortcut for c.Params.ByName(key)
router.GET("/user/:id", func(c *gin.Context) { // a GET request to /user/john id := c.Param("id") // id == "john" // a GET request to /user/john/ id := c.Param("id") // id == "/john/" })
PostForm returns the specified key from a POST urlencoded form or multipart form when it exists, otherwise it returns an empty string `("")`.
PostFormArray returns a slice of strings for a given form key. The length of the slice depends on the number of params with the given key.
PostFormMap returns a map for a given form key.
ProtoBuf serializes the given struct as ProtoBuf into the response body.
PureJSON serializes the given struct as JSON into the response body. PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
Query returns the keyed url query value if it exists, otherwise it returns an empty string `("")`. It is shortcut for `c.Request.URL.Query().Get(key)`
GET /path?id=1234&name=Manu&value= c.Query("id") == "1234" c.Query("name") == "Manu" c.Query("value") == "" c.Query("wtf") == ""
QueryArray returns a slice of strings for a given query key. The length of the slice depends on the number of params with the given key.
QueryMap returns a map for a given query key.
Redirect returns an HTTP redirect to the specific location.
RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
Render writes the response headers and calls render.Render to render data.
SSEvent writes a Server-Sent Event into the body stream.
SaveUploadedFile uploads the form file to specific dst.
SecureJSON serializes the given struct as Secure JSON into the response body. Default prepends "while(1)," to response body if the given struct is array values. It also sets the Content-Type as "application/json".
Set is used to store a new key/value pair exclusively for this context. It also lazy initializes c.Keys if it was not used previously.
SetAccepted sets Accept header data.
SetCookie adds a Set-Cookie header to the ResponseWriter's headers. The provided cookie must have a valid Name. Invalid cookies may be silently dropped.
ShouldBind checks the Method and Content-Type to select a binding engine automatically, Depending on the "Content-Type" header different bindings are used, for example:
"application/json" --> JSON binding "application/xml" --> XML binding
It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer. Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
func (*Context) ShouldBindBodyWith ¶ added in v1.3.0ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request body into the context, and reuse when it is called again.
NOTE: This method reads the body before binding. So you should use ShouldBindWith for better performance if you need to call only once.
func (*Context) ShouldBindBodyWithJSON ¶ added in v1.10.0ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON).
func (*Context) ShouldBindBodyWithTOML ¶ added in v1.10.0ShouldBindBodyWithTOML is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML).
func (*Context) ShouldBindBodyWithXML ¶ added in v1.10.0ShouldBindBodyWithXML is a shortcut for c.ShouldBindBodyWith(obj, binding.XML).
func (*Context) ShouldBindBodyWithYAML ¶ added in v1.10.0ShouldBindBodyWithYAML is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML).
ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
ShouldBindUri binds the passed struct pointer using the specified binding engine.
ShouldBindWith binds the passed struct pointer using the specified binding engine. See the binding package.
ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
Status sets the HTTP response code.
Stream sends a streaming response and returns a boolean indicates "Is client disconnected in middle of stream"
String writes the given string into the response body.
TOML serializes the given struct as TOML into the response body.
Value returns the value associated with this context for key, or nil if no value is associated with key. Successive calls to Value with the same key returns the same result.
XML serializes the given struct as XML into the response body. It also sets the Content-Type as "application/xml".
YAML serializes the given struct as YAML into the response body.
Engine is the framework's instance, it contains the muxer, middleware and configuration settings. Create an instance of Engine, by using New() or Default()
Default returns an Engine instance with the Logger and Recovery middleware already attached.
New returns a new blank Engine instance without any middleware attached. By default, the configuration is: - RedirectTrailingSlash: true - RedirectFixedPath: false - HandleMethodNotAllowed: false - ForwardedByClientIP: true - UseRawPath: false - UnescapePathValues: true
Delims sets template left and right delims and returns an Engine instance.
func (*Engine) HandleContext ¶ added in v1.3.0HandleContext re-enters a context that has been rewritten. This can be done by setting c.Request.URL.Path to your new target. Disclaimer: You can loop yourself to deal with this, use wisely.
func (*Engine) Handler ¶ added in v1.8.0LoadHTMLFiles loads a slice of HTML files and associates the result with HTML renderer.
LoadHTMLGlob loads HTML files identified by glob pattern and associates the result with HTML renderer.
NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true.
NoRoute adds handlers for NoRoute. It returns a 404 code by default.
Routes returns a slice of registered routes, including some useful information, such as: the http method, path and the handler name.
Run attaches the router to a http.Server and starts listening and serving HTTP requests. It is a shortcut for http.ListenAndServe(addr, router) Note: this method will block the calling goroutine indefinitely unless an error happens.
RunFd attaches the router to a http.Server and starts listening and serving HTTP requests through the specified file descriptor. Note: this method will block the calling goroutine indefinitely unless an error happens.
RunListener attaches the router to a http.Server and starts listening and serving HTTP requests through the specified net.Listener
RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) Note: this method will block the calling goroutine indefinitely unless an error happens.
RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests through the specified unix socket (i.e. a file). Note: this method will block the calling goroutine indefinitely unless an error happens.
SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON.
ServeHTTP conforms to the http.Handler interface.
SetFuncMap sets the FuncMap used for template.FuncMap.
SetHTMLTemplate associate a template with HTML renderer.
SetTrustedProxies set a list of network origins (IPv4 addresses, IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs) from which to trust request's headers that contain alternative client IP when `(*gin.Engine).ForwardedByClientIP` is `true`. `TrustedProxies` feature is enabled by default, and it also trusts all proxies by default. If you want to disable this feature, use Engine.SetTrustedProxies(nil), then Context.ClientIP() will return the remote address directly.
Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be included in the handlers chain for every single request. Even 404, 405, static files... For example, this is the right place for a logger or error management middleware.
With returns a new Engine instance with the provided options.
Error represents a error's specification.
Error implements the error interface.
JSON creates a properly formatted JSON
MarshalJSON implements the json.Marshaller interface.
SetMeta sets the error's meta data.
SetType sets the error's type.
Unwrap returns the wrapped error, to allow interoperability with errors.Is(), errors.As() and errors.Unwrap()
ErrorType is an unsigned 64-bit error code as defined in the gin spec.
H is a shortcut for map[string]any
MarshalXML allows type H to be used with xml.Marshal.
type HandlerFunc ¶HandlerFunc defines the handler used by gin middleware as return value.
BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where the key is the user name and the value is the password.
BasicAuthForProxy returns a Basic HTTP Proxy-Authorization middleware. If the realm is empty, "Proxy Authorization Required" will be used by default.
BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where the key is the user name and the value is the password, as well as the name of the Realm. If the realm is empty, "Authorization Required" will be used by default. (see http://tools.ietf.org/html/rfc2617#section-1.2)
Bind is a helper function for given interface object and returns a Gin middleware.
CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
ErrorLogger returns a HandlerFunc for any error type.
ErrorLoggerT returns a HandlerFunc for a given error type.
Logger instances a Logger middleware that will write the logs to gin.DefaultWriter. By default, gin.DefaultWriter = os.Stdout.
LoggerWithConfig instance a Logger middleware with config.
LoggerWithFormatter instance a Logger middleware with the specified log format function.
LoggerWithWriter instance a Logger middleware with the specified writer buffer. Example: os.Stdout, a file opened in write mode, a socket...
Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
WrapF is a helper function for wrapping http.HandlerFunc and returns a Gin middleware.
WrapH is a helper function for wrapping http.Handler and returns a Gin middleware.
type HandlersChain ¶HandlersChain defines a HandlerFunc slice.
func (HandlersChain) Last ¶Last returns the last handler in the chain. i.e. the last handler is the main one.
IRouter defines all router handle interface includes single and group router.
type IRoutes interface { Use(...HandlerFunc) IRoutes Handle(string, string, ...HandlerFunc) IRoutes Any(string, ...HandlerFunc) IRoutes GET(string, ...HandlerFunc) IRoutes POST(string, ...HandlerFunc) IRoutes DELETE(string, ...HandlerFunc) IRoutes PATCH(string, ...HandlerFunc) IRoutes PUT(string, ...HandlerFunc) IRoutes OPTIONS(string, ...HandlerFunc) IRoutes HEAD(string, ...HandlerFunc) IRoutes Match([]string, string, ...HandlerFunc) IRoutes StaticFile(string, string) IRoutes StaticFileFS(string, string, http.FileSystem) IRoutes Static(string, string) IRoutes StaticFS(string, http.FileSystem) IRoutes }
IRoutes defines all router handle interface.
LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
LogFormatterParams is the structure any formatter will be handed when time to log comes
IsOutputColor indicates whether can colors be outputted to the log.
MethodColor is the ANSI color for appropriately logging http method to a terminal.
ResetColor resets all escape attributes.
StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
LoggerConfig defines the config for Logger middleware.
Negotiate contains all negotiations data.
OptionFunc defines the function to change the default configuration
Param is a single URL parameter, consisting of a key and a value.
Params is a Param-slice, as returned by the router. The slice is ordered, the first URL parameter is also the first slice value. It is therefore safe to read values by the index.
ByName returns the value of the first Param which key matches the given name. If no matching Param is found, an empty string is returned.
Get returns the value of the first Param which key matches the given name and a boolean true. If no matching Param is found, an empty string is returned and a boolean false .
RecoveryFunc defines the function passable to CustomRecovery.
RouteInfo represents a request route's specification which contains method and path and its handler.
RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix and an array of handlers (middleware).
Any registers a route that matches all the HTTP methods. GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
BasePath returns the base path of router group. For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api".
DELETE is a shortcut for router.Handle("DELETE", path, handlers).
GET is a shortcut for router.Handle("GET", path, handlers).
Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix. For example, all the routes that use a common middleware for authorization could be grouped.
HEAD is a shortcut for router.Handle("HEAD", path, handlers).
func (*RouterGroup) Handle ¶Handle registers a new request handle and middleware with the given path and method. The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes. See the example code in GitHub.
For GET, POST, PUT, PATCH and DELETE requests the respective shortcut functions can be used.
This function is intended for bulk loading and to allow the usage of less frequently used, non-standardized or custom methods (e.g. for internal communication with a proxy).
Match registers a route that matches the specified methods that you declared.
OPTIONS is a shortcut for router.Handle("OPTIONS", path, handlers).
PATCH is a shortcut for router.Handle("PATCH", path, handlers).
POST is a shortcut for router.Handle("POST", path, handlers).
PUT is a shortcut for router.Handle("PUT", path, handlers).
Static serves files from the given file system root. Internally a http.FileServer is used, therefore http.NotFound is used instead of the Router's NotFound handler. To use the operating system's file system implementation, use :
router.Static("/static", "/var/www")
StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead. Gin by default uses: gin.Dir()
StaticFile registers a single route in order to serve a single file of the local filesystem. router.StaticFile("favicon.ico", "./resources/favicon.ico")
StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead.. router.StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false}) Gin by default uses: gin.Dir()
Use adds middleware to the group, see example code in GitHub.
RoutesInfo defines a RouteInfo slice.
Skipper is a function to skip logs based on provided Context
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4