1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-11 05:55:57 +00:00

reorganization of _examples and add some new examples such as iris+groupcache+mysql+docker

Former-commit-id: ed635ee95de7160cde11eaabc0c1dcb0e460a620
This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-06-07 15:26:06 +03:00
parent 9fdcb4c7fb
commit ed45c77be5
328 changed files with 4262 additions and 41621 deletions

View File

@@ -0,0 +1,39 @@
// Package main shows how you can use the `WriteWithExpiration`
// based on the "modtime", if it's newer than the request header then
// it will refresh the contents, otherwise will let the client (99.9% the browser)
// to handle the cache mechanism, it's faster than iris.Cache because server-side
// has nothing to do and no need to store the responses in the memory.
package main
import (
"time"
"github.com/kataras/iris/v12"
)
const refreshEvery = 10 * time.Second
func main() {
app := iris.New()
app.Use(iris.Cache304(refreshEvery))
// same as:
// app.Use(func(ctx iris.Context) {
// now := time.Now()
// if modified, err := ctx.CheckIfModifiedSince(now.Add(-refresh)); !modified && err == nil {
// ctx.WriteNotModified()
// return
// }
// ctx.SetLastModified(now)
// ctx.Next()
// })
app.Get("/", greet)
app.Listen(":8080")
}
func greet(ctx iris.Context) {
ctx.Header("X-Custom", "my custom header")
ctx.Writef("Hello World! %s", time.Now())
}

View File

@@ -0,0 +1,80 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/cache"
)
var markdownContents = []byte(`## Hello Markdown
This is a sample of Markdown contents
Features
--------
All features of Sundown are supported, including:
* **Compatibility**. The Markdown v1.0.3 test suite passes with
the --tidy option. Without --tidy, the differences are
mostly in whitespace and entity escaping, where blackfriday is
more consistent and cleaner.
* **Common extensions**, including table support, fenced code
blocks, autolinks, strikethroughs, non-strict emphasis, etc.
* **Safety**. Blackfriday is paranoid when parsing, making it safe
to feed untrusted user input without fear of bad things
happening. The test suite stress tests this and there are no
known inputs that make it crash. If you find one, please let me
know and send me the input that does it.
NOTE: "safety" in this context means *runtime safety only*. In order to
protect yourself against JavaScript injection in untrusted content, see
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
* **Fast processing**. It is fast enough to render on-demand in
most web applications without having to cache the output.
* **Routine safety**. You can run multiple parsers in different
goroutines without ill effect. There is no dependence on global
shared state.
* **Minimal dependencies**. Blackfriday only depends on standard
library packages in Go. The source code is pretty
self-contained, so it is easy to add to any project, including
Google App Engine projects.
* **Standards compliant**. Output successfully validates using the
W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
[this is a link](https://github.com/kataras/iris) `)
// Cache should not be used on handlers that contain dynamic data.
// Cache is a good and a must-feature on static content, i.e "about page" or for a whole blog site.
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
app.Get("/", cache.Handler(10*time.Second), writeMarkdown)
// saves its content on the first request and serves it instead of re-calculating the content.
// After 10 seconds it will be cleared and reset.
app.Listen(":8080")
}
func writeMarkdown(ctx iris.Context) {
// tap multiple times the browser's refresh button and you will
// see this println only once every 10 seconds.
println("Handler executed. Content refreshed.")
ctx.Markdown(markdownContents)
}
/* Note that `HandleDir` does use the browser's disk caching by-default
therefore, register the cache handler AFTER any HandleDir calls,
for a faster solution that server doesn't need to keep track of the response
navigate to https://github.com/kataras/iris/blob/master/_examples/cache/client-side/main.go */

View File

@@ -0,0 +1,114 @@
// Package main contains three different ways to render content based on the client's accepted.
package main
import "github.com/kataras/iris/v12"
type testdata struct {
Name string `json:"name" xml:"Name"`
Age int `json:"age" xml:"Age"`
}
func newApp() *iris.Application {
app := iris.New()
app.Logger().SetLevel("debug")
// app.Use(func(ctx iris.Context) {
// requestedMime := ctx.URLParamDefault("type", "application/json")
//
// ctx.Negotiation().Accept.Override().MIME(requestedMime, nil)
// ctx.Next()
// })
app.Get("/resource", func(ctx iris.Context) {
data := testdata{
Name: "test name",
Age: 26,
}
// Server allows response only JSON and XML. These values
// are compared with the clients mime needs. Iris comes with default mime types responses
// but you can add a custom one by the `Negotiation().Mime(mime, content)` method,
// same for the "accept".
// You can also pass a custom ContentSelector(mime string) or ContentNegotiator to the
// `Context.Negotiate` method if you want to perform more advanced things.
//
//
// By-default the client accept mime is retrieved by the "Accept" header
// Indeed you can override or update it by `Negotiation().Accept.XXX` i.e
// ctx.Negotiation().Accept.Override().XML()
//
// All these values can change inside middlewares, the `Negotiation().Override()` and `.Accept.Override()`
// can override any previously set values.
// Order matters, if the client accepts anything (*/*)
// then the first prioritized mime's response data will be rendered.
ctx.Negotiation().JSON().XML()
// Accept-Charset vs:
ctx.Negotiation().Charset("utf-8", "iso-8859-7")
// Alternatively you can define the content/data per mime type
// anywhere in the handlers chain using the optional "v" variadic
// input argument of the Context.Negotiation().JSON,XML,YAML,Binary,Text,HTML(...) and e.t.c
// example (order matters):
// ctx.Negotiation().JSON(data).XML(data).Any("content for */*")
// ctx.Negotiate(nil)
// if not nil passed in the `Context.Negotiate` method
// then it overrides any contents made by the negotitation builder above.
_, err := ctx.Negotiate(data)
if err != nil {
ctx.Writef("%v", err)
}
})
app.Get("/resource2", func(ctx iris.Context) {
jsonAndXML := testdata{
Name: "test name",
Age: 26,
}
// I prefer that one, as it gives me the freedom to modify
// response data per accepted mime content type on middlewares as well.
ctx.Negotiation().
JSON(jsonAndXML).
XML(jsonAndXML).
HTML("<h1>Test Name</h1><h2>Age 26</h2>")
ctx.Negotiate(nil)
})
app.Get("/resource3", func(ctx iris.Context) {
// If that line is missing and the requested
// mime type of content is */* or application/xml or application/json
// then 406 Not Acceptable http error code will be rendered instead.
//
// We also add the "gzip" algorithm as an option to encode
// resources on send.
ctx.Negotiation().JSON().XML().HTML().EncodingGzip()
jsonAndXML := testdata{
Name: "test name",
Age: 26,
}
// Prefer that way instead of the '/resource2' above
// if "iris.N" is a static one and can be declared
// outside of a handler.
ctx.Negotiate(iris.N{
// Text: for text/plain,
// Markdown: for text/mardown,
// Binary: for application/octet-stream,
// YAML: for application/x-yaml,
// JSONP: for text/javascript
// Other: for anything else,
JSON: jsonAndXML, // for application/json
XML: jsonAndXML, // for application/xml or text/xml
HTML: "<h1>Test Name</h1><h2>Age 26</h2>", // for text/html
})
})
return app
}
func main() {
app := newApp()
app.Listen(":8080")
}

View File

@@ -0,0 +1,80 @@
package main
import (
"bytes"
"compress/gzip"
"encoding/xml"
"io/ioutil"
"testing"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/httptest"
)
func TestContentNegotiation(t *testing.T) {
var (
expectedJSONResponse = testdata{
Name: "test name",
Age: 26,
}
expectedXMLResponse, _ = xml.Marshal(expectedJSONResponse)
expectedHTMLResponse = "<h1>Test Name</h1><h2>Age 26</h2>"
)
app := newApp()
app.Configure(iris.WithOptimizations)
e := httptest.New(t, app)
e.GET("/resource").WithHeader("Accept", "application/json").
Expect().Status(httptest.StatusOK).
ContentType("application/json", "utf-8").
JSON().Equal(expectedJSONResponse)
e.GET("/resource").WithHeader("Accept", "application/xml").WithHeader("Accept-Charset", "iso-8859-7").
Expect().Status(httptest.StatusOK).
ContentType("application/xml", "iso-8859-7").
Body().Equal(string(expectedXMLResponse))
e.GET("/resource2").WithHeader("Accept", "application/json").
Expect().Status(httptest.StatusOK).
ContentType("application/json", "utf-8").
JSON().Equal(expectedJSONResponse)
e.GET("/resource2").WithHeader("Accept", "application/xml").
Expect().Status(httptest.StatusOK).
ContentType("application/xml", "utf-8").
Body().Equal(string(expectedXMLResponse))
e.GET("/resource2").WithHeader("Accept", "text/html").
Expect().Status(httptest.StatusOK).
ContentType("text/html", "utf-8").
Body().Equal(expectedHTMLResponse)
e.GET("/resource3").WithHeader("Accept", "application/json").
Expect().Status(httptest.StatusOK).
ContentType("application/json", "utf-8").
JSON().Equal(expectedJSONResponse)
e.GET("/resource3").WithHeader("Accept", "application/xml").
Expect().Status(httptest.StatusOK).
ContentType("application/xml", "utf-8").
Body().Equal(string(expectedXMLResponse))
// test html with "gzip" encoding algorithm.
rawGzipResponse := e.GET("/resource3").WithHeader("Accept", "text/html").
WithHeader("Accept-Encoding", "gzip").
Expect().Status(httptest.StatusOK).
ContentType("text/html", "utf-8").
ContentEncoding("gzip").
Body().Raw()
zr, err := gzip.NewReader(bytes.NewReader([]byte(rawGzipResponse)))
if err != nil {
t.Fatal(err)
}
rawResponse, err := ioutil.ReadAll(zr)
if err != nil {
t.Fatal(err)
}
if expected, got := expectedHTMLResponse, string(rawResponse); expected != got {
t.Fatalf("expected response to be:\n%s but got:\n%s", expected, got)
}
}

View File

@@ -0,0 +1,50 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/r3labs/sse"
)
// First of all install the sse third-party package (you can use other if you don't like this approach or go ahead to the "sse" example)
// $ go get -u github.com/r3labs/sse
func main() {
app := iris.New()
s := sse.New()
/*
This creates a new stream inside of the scheduler.
Seeing as there are no consumers, publishing a message
to this channel will do nothing.
Clients can connect to this stream once the iris handler is started
by specifying stream as a url parameter, like so:
http://localhost:8080/events?stream=messages
*/
s.CreateStream("messages")
app.Any("/events", iris.FromStd(s.HTTPHandler))
go func() {
// You design when to send messages to the client,
// here we just wait 5 seconds to send the first message
// in order to give u time to open a browser window...
time.Sleep(5 * time.Second)
// Publish a payload to the stream.
s.Publish("messages", &sse.Event{
Data: []byte("ping"),
})
time.Sleep(3 * time.Second)
s.Publish("messages", &sse.Event{
Data: []byte("second message"),
})
time.Sleep(2 * time.Second)
s.Publish("messages", &sse.Event{
Data: []byte("third message"),
})
}() // ...
app.Listen(":8080")
}
/* For a golang SSE client you can look at: https://github.com/r3labs/sse#example-client */

View File

@@ -0,0 +1,192 @@
// Package main shows how to send continuous event messages to the clients through SSE via a broker.
// Read details at: https://www.w3schools.com/htmL/html5_serversentevents.asp and
// https://robots.thoughtbot.com/writing-a-server-sent-events-server-in-go
package main
import (
"encoding/json"
"fmt"
"time"
"github.com/kataras/golog"
"github.com/kataras/iris/v12"
)
// A Broker holds open client connections,
// listens for incoming events on its Notifier channel
// and broadcast event data to all registered connections.
type Broker struct {
// Events are pushed to this channel by the main events-gathering routine.
Notifier chan []byte
// New client connections.
newClients chan chan []byte
// Closed client connections.
closingClients chan chan []byte
// Client connections registry.
clients map[chan []byte]bool
}
// NewBroker returns a new broker factory.
func NewBroker() *Broker {
b := &Broker{
Notifier: make(chan []byte, 1),
newClients: make(chan chan []byte),
closingClients: make(chan chan []byte),
clients: make(map[chan []byte]bool),
}
// Set it running - listening and broadcasting events.
go b.listen()
return b
}
// Listen on different channels and act accordingly.
func (b *Broker) listen() {
for {
select {
case s := <-b.newClients:
// A new client has connected.
// Register their message channel.
b.clients[s] = true
golog.Infof("Client added. %d registered clients", len(b.clients))
case s := <-b.closingClients:
// A client has dettached and we want to
// stop sending them messages.
delete(b.clients, s)
golog.Infof("Removed client. %d registered clients", len(b.clients))
case event := <-b.Notifier:
// We got a new event from the outside!
// Send event to all connected clients.
for clientMessageChan := range b.clients {
clientMessageChan <- event
}
}
}
}
func (b *Broker) ServeHTTP(ctx iris.Context) {
// Make sure that the writer supports flushing.
flusher, ok := ctx.ResponseWriter().Flusher()
if !ok {
ctx.StatusCode(iris.StatusHTTPVersionNotSupported)
ctx.WriteString("Streaming unsupported!")
return
}
// Set the headers related to event streaming, you can omit the "application/json" if you send plain text.
// If you develop a go client, you must have: "Accept" : "application/json, text/event-stream" header as well.
ctx.ContentType("application/json, text/event-stream")
ctx.Header("Cache-Control", "no-cache")
ctx.Header("Connection", "keep-alive")
// We also add a Cross-origin Resource Sharing header so browsers on different domains can still connect.
ctx.Header("Access-Control-Allow-Origin", "*")
// Each connection registers its own message channel with the Broker's connections registry.
messageChan := make(chan []byte)
// Signal the broker that we have a new connection.
b.newClients <- messageChan
// Listen to connection close and when the entire request handler chain exits(this handler here) and un-register messageChan.
ctx.OnClose(func() {
// Remove this client from the map of connected clients
// when this handler exits.
b.closingClients <- messageChan
})
// Block waiting for messages broadcast on this connection's messageChan.
for {
// Write to the ResponseWriter.
// Server Sent Events compatible.
ctx.Writef("data: %s\n\n", <-messageChan)
// or json: data:{obj}.
// Flush the data immediately instead of buffering it for later.
flusher.Flush()
}
}
type event struct {
Timestamp int64 `json:"timestamp"`
Message string `json:"message"`
}
const script = `<script type="text/javascript">
if(typeof(EventSource) !== "undefined") {
console.log("server-sent events supported");
var client = new EventSource("http://localhost:8080/events");
var index = 1;
client.onmessage = function (evt) {
console.log(evt);
// it's not required that you send and receive JSON, you can just output the "evt.data" as well.
dataJSON = JSON.parse(evt.data)
var table = document.getElementById("messagesTable");
var row = table.insertRow(index);
var cellTimestamp = row.insertCell(0);
var cellMessage = row.insertCell(1);
cellTimestamp.innerHTML = dataJSON.timestamp;
cellMessage.innerHTML = dataJSON.message;
index++;
window.scrollTo(0,document.body.scrollHeight);
};
} else {
document.getElementById("header").innerHTML = "<h2>SSE not supported by this client-protocol</h2>";
}
</script>`
func main() {
broker := NewBroker()
go func() {
for {
time.Sleep(2 * time.Second)
now := time.Now()
evt := event{
Timestamp: now.Unix(),
Message: fmt.Sprintf("Hello at %s", now.Format(time.RFC1123)),
}
evtBytes, err := json.Marshal(evt)
if err != nil {
golog.Error(err)
continue
}
broker.Notifier <- evtBytes
}
}()
app := iris.New()
app.Logger().SetLevel("debug")
app.Get("/", func(ctx iris.Context) {
ctx.HTML(
`<html><head><title>SSE</title>` + script + `</head>
<body>
<h1 id="header">Waiting for messages...</h1>
<table id="messagesTable" border="1">
<tr>
<th>Timestamp (server)</th>
<th>Message</th>
</tr>
</table>
</body>
</html>`)
})
app.Get("/events", broker.ServeHTTP)
// http://localhost:8080
// http://localhost:8080/events
app.Listen(":8080")
}

View File

@@ -0,0 +1,17 @@
<!-- you can just put that to your favourite browser -->
<html>
<head>
<title>SSE (javascript side)</title>
<script type="text/javascript">
var client = new EventSource("http://localhost:8080/events")
client.onmessage = function (evt) {
console.log(evt)
}
</script>
</head>
<body>
<h1>Open the browser's console(F12) and watch for incoming event messages</h1>
</body>
</html>

View File

@@ -0,0 +1,54 @@
package main
import (
"fmt" // just an optional helper
"io"
"time" // showcase the delay
"github.com/kataras/iris/v12"
)
func main() {
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.ContentType("text/html")
ctx.Header("Transfer-Encoding", "chunked")
i := 0
ints := []int{1, 2, 3, 5, 7, 9, 11, 13, 15, 17, 23, 29}
// Send the response in chunks and wait for half a second between each chunk.
ctx.StreamWriter(func(w io.Writer) bool {
fmt.Fprintf(w, "Message number %d<br>", ints[i])
time.Sleep(500 * time.Millisecond) // simulate delay.
if i == len(ints)-1 {
return false // close and flush
}
i++
return true // continue write
})
})
type messageNumber struct {
Number int `json:"number"`
}
app.Get("/alternative", func(ctx iris.Context) {
ctx.ContentType("application/json")
ctx.Header("Transfer-Encoding", "chunked")
i := 0
ints := []int{1, 2, 3, 5, 7, 9, 11, 13, 15, 17, 23, 29}
// Send the response in chunks and wait for half a second between each chunk.
for {
ctx.JSON(messageNumber{Number: ints[i]})
ctx.WriteString("\n")
time.Sleep(500 * time.Millisecond) // simulate delay.
if i == len(ints)-1 {
break
}
i++
ctx.ResponseWriter().Flush()
}
})
app.Listen(":8080")
}

View File

@@ -0,0 +1,54 @@
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
)
func main() {
app := iris.New()
// subdomains works with all available routers, like other features too.
app.Get("/", func(ctx iris.Context) {
ctx.BeginTransaction(func(t *context.Transaction) {
// OPTIONAl STEP: , if true then the next transictions will not be executed if this transiction fails
// t.SetScope(context.RequestTransactionScope)
// OPTIONAL STEP:
// create a new custom type of error here to keep track of the status code and reason message
err := context.NewTransactionErrResult()
// we should use t.Context if we want to rollback on any errors lives inside this function clojure.
t.Context().Text("Blablabla this should not be sent to the client because we will fill the err with a message and status")
// virtualize a fake error here, for the sake of the example
fail := true
if fail {
err.StatusCode = iris.StatusInternalServerError
// NOTE: if empty reason then the default or the custom http error will be fired (like ctx.FireStatusCode)
err.Reason = "Error: Virtual failure!!"
}
// OPTIONAl STEP:
// but useful if we want to post back an error message to the client if the transaction failed.
// if the reason is empty then the transaction completed successfully,
// otherwise we rollback the whole response writer's body,
// headers and cookies, status code and everything lives inside this transaction
t.Complete(err)
})
ctx.BeginTransaction(func(t *context.Transaction) {
t.Context().HTML("<h1>This will sent at all cases because it lives on different transaction and it doesn't fails</h1>")
// * if we don't have any 'throw error' logic then no need of scope.Complete()
})
// OPTIONALLY, depends on the usage:
// at any case, what ever happens inside the context's transactions send this to the client
ctx.HTML("<h1>Let's add a second html message to the response, " +
"if the transaction was failed and it was request scoped then this message would " +
"not been shown. But it has a transient scope(default) so, it is visible as expected!</h1>")
})
app.Listen(":8080")
}

View File

@@ -0,0 +1,24 @@
package main
import "github.com/kataras/iris/v12"
func main() {
app := iris.New()
// app.Use(iris.Gzip)
// func(ctx iris.Context) { ctx.Gzip(true/false)}
// OR:
app.Get("/", func(ctx iris.Context) {
ctx.WriteGzip([]byte("Hello World!"))
ctx.Header("X-Custom",
"Headers can be set here after WriteGzip as well, because the data are kept before sent to the client when using the context's GzipResponseWriter and ResponseRecorder.")
})
app.Get("/2", func(ctx iris.Context) {
// same as the `WriteGzip`.
// However GzipResponseWriter gives you more options, like
// reset data, disable and more, look its methods.
ctx.GzipResponseWriter().WriteString("Hello World!")
})
app.Listen(":8080")
}

View File

@@ -0,0 +1,117 @@
package main
import (
"encoding/xml"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
)
// User example struct for json and msgpack.
type User struct {
Firstname string `json:"firstname" msgpack:"firstname"`
Lastname string `json:"lastname" msgpack:"lastname"`
City string `json:"city" msgpack:"city"`
Age int `json:"age" msgpack:"age"`
}
// ExampleXML just a test struct to view represents xml content-type
type ExampleXML struct {
XMLName xml.Name `xml:"example"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
// ExampleYAML just a test struct to write yaml to the client.
type ExampleYAML struct {
Name string `yaml:"name"`
ServerAddr string `yaml:"ServerAddr"`
}
func main() {
app := iris.New()
// Read
app.Post("/decode", func(ctx iris.Context) {
// Read https://github.com/kataras/iris/blob/master/_examples/request-body/read-json/main.go as well.
var user User
ctx.ReadJSON(&user)
ctx.Writef("%s %s is %d years old and comes from %s!", user.Firstname, user.Lastname, user.Age, user.City)
})
// Write
app.Get("/encode", func(ctx iris.Context) {
u := User{
Firstname: "John",
Lastname: "Doe",
City: "Neither FBI knows!!!",
Age: 25,
}
// Manually setting a content type: ctx.ContentType("text/javascript")
ctx.JSON(u)
})
// Other content types,
app.Get("/binary", func(ctx iris.Context) {
// useful when you want force-download of contents of raw bytes form.
ctx.Binary([]byte("Some binary data here."))
})
app.Get("/text", func(ctx iris.Context) {
ctx.Text("Plain text here")
})
app.Get("/json", func(ctx iris.Context) {
ctx.JSON(map[string]string{"hello": "json"}) // or myjsonStruct{hello:"json}
})
app.Get("/jsonp", func(ctx iris.Context) {
ctx.JSONP(map[string]string{"hello": "jsonp"}, context.JSONP{Callback: "callbackName"})
})
app.Get("/xml", func(ctx iris.Context) {
ctx.XML(ExampleXML{One: "hello", Two: "xml"}) // or iris.Map{"One":"hello"...}
})
app.Get("/markdown", func(ctx iris.Context) {
ctx.Markdown([]byte("# Hello Dynamic Markdown -- iris"))
})
app.Get("/yaml", func(ctx iris.Context) {
ctx.YAML(ExampleYAML{Name: "Iris", ServerAddr: "localhost:8080"})
})
app.Get("/msgpack", func(ctx iris.Context) {
u := User{
Firstname: "John",
Lastname: "Doe",
City: "Neither FBI knows!!!",
Age: 25,
}
ctx.MsgPack(u)
})
// http://localhost:8080/decode
// http://localhost:8080/encode
//
// http://localhost:8080/binary
// http://localhost:8080/text
// http://localhost:8080/json
// http://localhost:8080/jsonp
// http://localhost:8080/xml
// http://localhost:8080/markdown
// http://localhost:8080/msgpack
//
// `iris.WithOptimizations` is an optional configurator,
// if passed to the `Run` then it will ensure that the application
// response to the client as fast as possible.
//
//
// `iris.WithoutServerError` is an optional configurator,
// if passed to the `Run` then it will not print its passed error as an actual server error.
app.Listen(":8080", iris.WithOptimizations)
}