mirror of
https://github.com/kataras/iris.git
synced 2025-12-21 03:47:04 +00:00
Add More Examples & Categorized in Folders & TOC
Former-commit-id: ce4d711a75a4ba08ffab075e6baa88724725885b
This commit is contained in:
35
_examples/examples/advanced/cloud-editor/main.go
Normal file
35
_examples/examples/advanced/cloud-editor/main.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/typescript" // optinally
|
||||
"gopkg.in/kataras/iris.v6/adaptors/typescript/editor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Adapt(iris.DevLogger())
|
||||
app.Adapt(httprouter.New()) // adapt a router, order doesn't matters
|
||||
|
||||
// optionally but good to have, I didn't put inside editor or the editor in the typescript compiler adaptors
|
||||
// because you may use tools like gulp and you may use the editor without the typescript compiler adaptor.
|
||||
// but if you need auto-compilation on .ts, we have a solution:
|
||||
ts := typescript.New()
|
||||
ts.Config.Dir = "./www/scripts/"
|
||||
app.Adapt(ts) // adapt the typescript compiler adaptor
|
||||
|
||||
editorConfig := editor.Config{
|
||||
Hostname: "127.0.0.1",
|
||||
Port: 4444,
|
||||
WorkingDir: "./www/scripts/", // "/path/to/the/client/side/directory/",
|
||||
Username: "myusername",
|
||||
Password: "mypassword",
|
||||
}
|
||||
e := editor.New(editorConfig)
|
||||
app.Adapt(e) // adapt the editor
|
||||
|
||||
app.StaticWeb("/", "./www") // serve the index.html
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
||||
8
_examples/examples/advanced/cloud-editor/www/index.html
Normal file
8
_examples/examples/advanced/cloud-editor/www/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Load my script (lawl)</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="scripts/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
16
_examples/examples/advanced/cloud-editor/www/scripts/app.ts
Normal file
16
_examples/examples/advanced/cloud-editor/www/scripts/app.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
class User{
|
||||
private name: string;
|
||||
|
||||
constructor(fullname:string) {
|
||||
this.name = fullname;
|
||||
}
|
||||
|
||||
Hi(msg: string): string {
|
||||
return msg + " " + this.name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var user = new User("kataras");
|
||||
var hi = user.Hi("Hello");
|
||||
window.alert(hi);
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"sourceMap": false,
|
||||
"target": "ES5",
|
||||
"noEmit": false,
|
||||
"watch":true,
|
||||
"noEmitOnError": true,
|
||||
"experimentalDecorators": false,
|
||||
"outDir": "./",
|
||||
"charset": "UTF-8",
|
||||
"noLib": false,
|
||||
"diagnostics": true,
|
||||
"declaration": false
|
||||
},
|
||||
"files": [
|
||||
"./app.ts"
|
||||
]
|
||||
}
|
||||
41
_examples/examples/advanced/httptest/main.go
Normal file
41
_examples/examples/advanced/httptest/main.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/sessions"
|
||||
)
|
||||
|
||||
func newApp() *iris.Framework {
|
||||
app := iris.New()
|
||||
app.Adapt(httprouter.New())
|
||||
app.Adapt(sessions.New(sessions.Config{Cookie: "mysessionid"}))
|
||||
|
||||
app.Get("/hello", func(ctx *iris.Context) {
|
||||
sess := ctx.Session()
|
||||
if !sess.HasFlash() /* or sess.GetFlash("name") == "", same thing here */ {
|
||||
ctx.HTML(iris.StatusUnauthorized, "<h1> Unauthorized Page! </h1>")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(iris.StatusOK, iris.Map{
|
||||
"Message": "Hello",
|
||||
"From": sess.GetFlash("name"),
|
||||
})
|
||||
})
|
||||
|
||||
app.Post("/login", func(ctx *iris.Context) {
|
||||
sess := ctx.Session()
|
||||
if !sess.HasFlash() {
|
||||
sess.SetFlash("name", ctx.FormValue("name"))
|
||||
}
|
||||
// let's no redirect, just set the flash message, nothing more.
|
||||
})
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := newApp()
|
||||
app.Listen(":8080")
|
||||
}
|
||||
32
_examples/examples/advanced/httptest/main_test.go
Normal file
32
_examples/examples/advanced/httptest/main_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gopkg.in/kataras/iris.v6/httptest"
|
||||
)
|
||||
|
||||
// $ cd _example
|
||||
// $ go test -v
|
||||
func TestNewApp(t *testing.T) {
|
||||
app := newApp()
|
||||
e := httptest.New(app, t)
|
||||
|
||||
// test nauthorized
|
||||
e.GET("/hello").Expect().Status(401).Body().Equal("<h1> Unauthorized Page! </h1>")
|
||||
// test our login flash message
|
||||
name := "myname"
|
||||
e.POST("/login").WithFormField("name", name).Expect().Status(200)
|
||||
// test the /hello again with the flash (a message which deletes itself after it has been shown to the user)
|
||||
// setted on /login previously.
|
||||
expectedResponse := map[string]interface{}{
|
||||
"Message": "Hello",
|
||||
"From": name,
|
||||
}
|
||||
e.GET("/hello").Expect().Status(200).JSON().Equal(expectedResponse)
|
||||
// test /hello nauthorized again, it should be return 401 now (flash should be removed)
|
||||
e.GET("/hello").Expect().Status(401).Body().Equal("<h1> Unauthorized Page! </h1>")
|
||||
}
|
||||
|
||||
// for advanced test examples navigate there:
|
||||
// https://github.com/gavv/httpexpect/blob/master/_examples/iris_test.go
|
||||
170
_examples/examples/advanced/online-visitors/main.go
Normal file
170
_examples/examples/advanced/online-visitors/main.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/view"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
app *iris.Framework
|
||||
ws websocket.Server
|
||||
)
|
||||
|
||||
func init() {
|
||||
// init the server instance
|
||||
app = iris.New()
|
||||
// adapt a logger in dev mode
|
||||
app.Adapt(iris.DevLogger())
|
||||
// adapt router
|
||||
app.Adapt(httprouter.New())
|
||||
// adapt templaes
|
||||
app.Adapt(view.HTML("./templates", ".html").Reload(true))
|
||||
// adapt websocket
|
||||
ws = websocket.New(websocket.Config{Endpoint: "/my_endpoint"})
|
||||
ws.OnConnection(HandleWebsocketConnection)
|
||||
app.Adapt(ws)
|
||||
}
|
||||
|
||||
type page struct {
|
||||
PageID string
|
||||
}
|
||||
|
||||
func main() {
|
||||
app.StaticWeb("/js", "./static/assets/js")
|
||||
|
||||
h := func(ctx *iris.Context) {
|
||||
ctx.Render("index.html", page{PageID: "index page"})
|
||||
}
|
||||
|
||||
h2 := func(ctx *iris.Context) {
|
||||
ctx.Render("other.html", page{PageID: "other page"})
|
||||
}
|
||||
|
||||
// Open some browser tabs/or windows
|
||||
// and navigate to
|
||||
// http://localhost:8080/ and http://localhost:8080/other
|
||||
// Each page has its own online-visitors counter.
|
||||
app.Get("/", h)
|
||||
app.Get("/other", h2)
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
type pageView struct {
|
||||
source string
|
||||
count uint64
|
||||
}
|
||||
|
||||
func (v *pageView) increment() {
|
||||
atomic.AddUint64(&v.count, 1)
|
||||
}
|
||||
|
||||
func (v *pageView) decrement() {
|
||||
oldCount := v.count
|
||||
if oldCount > 0 {
|
||||
atomic.StoreUint64(&v.count, oldCount-1)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *pageView) getCount() uint64 {
|
||||
val := atomic.LoadUint64(&v.count)
|
||||
return val
|
||||
}
|
||||
|
||||
type (
|
||||
pageViews []pageView
|
||||
)
|
||||
|
||||
func (v *pageViews) Add(source string) {
|
||||
args := *v
|
||||
n := len(args)
|
||||
for i := 0; i < n; i++ {
|
||||
kv := &args[i]
|
||||
if kv.source == source {
|
||||
kv.increment()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c := cap(args)
|
||||
if c > n {
|
||||
args = args[:n+1]
|
||||
kv := &args[n]
|
||||
kv.source = source
|
||||
kv.count = 1
|
||||
*v = args
|
||||
return
|
||||
}
|
||||
|
||||
kv := pageView{}
|
||||
kv.source = source
|
||||
kv.count = 1
|
||||
*v = append(args, kv)
|
||||
}
|
||||
|
||||
func (v *pageViews) Get(source string) *pageView {
|
||||
args := *v
|
||||
n := len(args)
|
||||
for i := 0; i < n; i++ {
|
||||
kv := &args[i]
|
||||
if kv.source == source {
|
||||
return kv
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *pageViews) Reset() {
|
||||
*v = (*v)[:0]
|
||||
}
|
||||
|
||||
var v pageViews
|
||||
|
||||
// HandleWebsocketConnection handles the online viewers per example(gist source)
|
||||
func HandleWebsocketConnection(c websocket.Connection) {
|
||||
|
||||
c.On("watch", func(pageSource string) {
|
||||
v.Add(pageSource)
|
||||
// join the socket to a room linked with the page source
|
||||
c.Join(pageSource)
|
||||
|
||||
viewsCount := v.Get(pageSource).getCount()
|
||||
if viewsCount == 0 {
|
||||
viewsCount++ // count should be always > 0 here
|
||||
}
|
||||
c.To(pageSource).Emit("watch", viewsCount)
|
||||
})
|
||||
|
||||
c.OnLeave(func(roomName string) {
|
||||
if roomName != c.ID() { // if the roomName it's not the connection iself
|
||||
// the roomName here is the source, this is the only room(except the connection's ID room) which we join the users to.
|
||||
pageV := v.Get(roomName)
|
||||
if pageV == nil {
|
||||
return // for any case that this room is not a pageView source
|
||||
}
|
||||
// decrement -1 the specific counter for this page source.
|
||||
pageV.decrement()
|
||||
// 1. open 30 tabs.
|
||||
// 2. close the browser.
|
||||
// 3. re-open the browser
|
||||
// 4. should be v.getCount() = 1
|
||||
// in order to achieve the previous flow we should decrement exactly when the user disconnects
|
||||
// but emit the result a little after, on a goroutine
|
||||
// getting all connections within this room and emit the online views one by one.
|
||||
// note:
|
||||
// we can also add a time.Sleep(2-3 seconds) inside the goroutine at the future if we don't need 'real-time' updates.
|
||||
go func(currentConnID string) {
|
||||
for _, conn := range ws.GetConnectionsByRoom(roomName) {
|
||||
if conn.ID() != currentConnID {
|
||||
conn.Emit("watch", pageV.getCount())
|
||||
}
|
||||
|
||||
}
|
||||
}(c.ID())
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
(function() {
|
||||
var socket = new Ws("ws://localhost:8080/my_endpoint");
|
||||
|
||||
socket.OnConnect(function () {
|
||||
socket.Emit("watch", PAGE_SOURCE);
|
||||
});
|
||||
|
||||
|
||||
socket.On("watch", function (onlineViews) {
|
||||
var text = "1 online view";
|
||||
if (onlineViews > 1) {
|
||||
text = onlineViews + " online views";
|
||||
}
|
||||
document.getElementById("online_views").innerHTML = text;
|
||||
});
|
||||
|
||||
socket.OnDisconnect(function () {
|
||||
document.getElementById("online_views").innerHTML = "you've been disconnected";
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,43 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Online visitors example</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, "San Francisco", "Helvetica Neue", "Noto", "Roboto", "Calibri Light", sans-serif;
|
||||
color: #212121;
|
||||
font-size: 1.0em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 750px;
|
||||
margin: auto;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#online_views {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<span id="online_views">1 online view</span>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
/* take the page source from our passed struct on .Render */
|
||||
var PAGE_SOURCE = {{ .PageID }}
|
||||
</script>
|
||||
|
||||
<script src="/iris-ws.js"></script>
|
||||
|
||||
<script src="/js/visitors.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Different page, different results</title>
|
||||
<style>
|
||||
#online_views {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<span id="online_views">1 online view</span>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
/* take the page source from our passed struct on .Render */
|
||||
var PAGE_SOURCE = {{ .PageID }}
|
||||
</script>
|
||||
|
||||
<script src="/iris-ws.js"></script>
|
||||
|
||||
<script src="/js/visitors.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
36
_examples/examples/advanced/typescript/main.go
Normal file
36
_examples/examples/advanced/typescript/main.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/typescript"
|
||||
)
|
||||
|
||||
// NOTE: Some machines don't allow to install typescript automatically, so if you don't have typescript installed
|
||||
// and the typescript adaptor doesn't works for you then follow the below steps:
|
||||
// 1. close the iris server
|
||||
// 2. open your terminal and execute: npm install -g typescript
|
||||
// 3. start your iris server, it should be work, as expected, now.
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Adapt(iris.DevLogger())
|
||||
app.Adapt(httprouter.New()) // adapt a router, order doesn't matters but before Listen.
|
||||
|
||||
ts := typescript.New()
|
||||
ts.Config.Dir = "./www/scripts"
|
||||
app.Adapt(ts) // adapt the typescript compiler adaptor
|
||||
|
||||
app.StaticWeb("/", "./www") // serve the index.html
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
// open http://localhost:8080
|
||||
// go to ./www/scripts/app.ts
|
||||
// make a change
|
||||
// reload the http://localhost:8080 and you should see the changes
|
||||
//
|
||||
// what it does?
|
||||
// - compiles the typescript files using default compiler options if not tsconfig found
|
||||
// - watches for changes on typescript files, if a change then it recompiles the .ts to .js
|
||||
//
|
||||
// same as you used to do with gulp-like tools, but here at Iris I do my bests to help GO developers.
|
||||
8
_examples/examples/advanced/typescript/www/index.html
Normal file
8
_examples/examples/advanced/typescript/www/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Load my script (lawl)</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="scripts/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
16
_examples/examples/advanced/typescript/www/scripts/app.ts
Normal file
16
_examples/examples/advanced/typescript/www/scripts/app.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
class User{
|
||||
private name: string;
|
||||
|
||||
constructor(fullname:string) {
|
||||
this.name = fullname;
|
||||
}
|
||||
|
||||
Hi(msg: string): string {
|
||||
return msg + " "+ this.name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var user = new User("kataras");
|
||||
var hi = user.Hi("Hello");
|
||||
window.alert(hi);
|
||||
316
_examples/examples/advanced/url-shortener/main.go
Normal file
316
_examples/examples/advanced/url-shortener/main.go
Normal file
@@ -0,0 +1,316 @@
|
||||
// Package main shows how you can create a simple URL SHortener using only Iris and BoltDB.
|
||||
//
|
||||
// $ go get github.com/boltdb/bolt/...
|
||||
// $ go run main.go
|
||||
// $ start http://localhost:8080
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/view"
|
||||
)
|
||||
|
||||
// a custom Iris event policy, which will run when server interruped (i.e control+C)
|
||||
// receives a func() error, most of packages are compatible with that on their Close/Shutdown/Cancel funcs.
|
||||
func releaser(r func() error) iris.EventPolicy {
|
||||
return iris.EventPolicy{
|
||||
Interrupted: func(app *iris.Framework) {
|
||||
if err := r(); err != nil {
|
||||
app.Log(iris.ProdMode, "error while releasing resources: "+err.Error())
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// assign a variable to the DB so we can use its features later
|
||||
db := NewDB("shortener.db")
|
||||
factory := NewFactory(DefaultGenerator, db)
|
||||
|
||||
app.Adapt(
|
||||
// print all kind of errors and logs at os.Stdout
|
||||
iris.DevLogger(),
|
||||
// use the httprouter, you can use adpaotrs/gorillamux if you want
|
||||
httprouter.New(),
|
||||
// serve the "./templates" directory's "*.html" files with the HTML std view engine.
|
||||
view.HTML("./templates", ".html").Reload(true),
|
||||
// `db.Close` is a `func() error` so it can be a `releaser` too.
|
||||
// Wrap the db.Close with the releaser in order to be released when app exits or control+C
|
||||
// You probably never saw that before, clever pattern which I am able to use only with Iris :)
|
||||
releaser(db.Close),
|
||||
)
|
||||
|
||||
// template funcs
|
||||
//
|
||||
// look ./templates/index.html#L16
|
||||
app.Adapt(iris.TemplateFuncsPolicy{"isPositive": func(n int) bool {
|
||||
if n > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}})
|
||||
|
||||
// Serve static files (css)
|
||||
app.StaticWeb("/static", "./resources")
|
||||
|
||||
app.Get("/", func(ctx *iris.Context) {
|
||||
ctx.MustRender("index.html", iris.Map{"url_count": db.Len()})
|
||||
})
|
||||
|
||||
// find and execute a short url by its key
|
||||
// used on http://localhost:8080/u/dsaoj41u321dsa
|
||||
execShortURL := func(ctx *iris.Context, key string) {
|
||||
if key == "" {
|
||||
ctx.EmitError(iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
value := db.Get(key)
|
||||
if value == "" {
|
||||
ctx.SetStatusCode(iris.StatusNotFound)
|
||||
ctx.Writef("Short URL for key: '%s' not found", key)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(value, iris.StatusTemporaryRedirect)
|
||||
}
|
||||
app.Get("/u/:shortkey", func(ctx *iris.Context) {
|
||||
execShortURL(ctx, ctx.Param("shortkey"))
|
||||
})
|
||||
|
||||
app.Post("/shorten", func(ctx *iris.Context) {
|
||||
data := make(map[string]interface{}, 0)
|
||||
formValue := ctx.FormValue("url")
|
||||
if formValue == "" {
|
||||
data["form_result"] = "You need to a enter a URL."
|
||||
} else {
|
||||
key, err := factory.Gen(formValue)
|
||||
if err != nil {
|
||||
data["form_result"] = "Invalid URL."
|
||||
} else {
|
||||
if err = db.Set(key, formValue); err != nil {
|
||||
data["form_result"] = "Internal error while saving the url"
|
||||
app.Log(iris.DevMode, "while saving url: "+err.Error())
|
||||
} else {
|
||||
ctx.SetStatusCode(iris.StatusOK)
|
||||
shortenURL := "http://" + app.Config.VHost + "/u/" + key
|
||||
data["form_result"] = template.HTML("<pre><a target='_new' href='" + shortenURL + "'>" + shortenURL + " </a></pre>")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
data["url_count"] = db.Len()
|
||||
ctx.Render("index.html", data)
|
||||
})
|
||||
|
||||
app.Listen("localhost:8080")
|
||||
}
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | |
|
||||
// | Store |
|
||||
// | |
|
||||
// +------------------------------------------------------------+
|
||||
|
||||
// Panic panics, change it if you don't want to panic on critical INITIALIZE-ONLY-ERRORS
|
||||
var Panic = func(v interface{}) {
|
||||
panic(v)
|
||||
}
|
||||
|
||||
// Store is the store interface for urls.
|
||||
// Note: no Del functionality.
|
||||
type Store interface {
|
||||
Set(key string, value string) error // error if something went wrong
|
||||
Get(key string) string // empty value if not found
|
||||
Len() int // should return the number of all the records/tables/buckets
|
||||
Close() error // release the store or ignore
|
||||
}
|
||||
|
||||
var (
|
||||
tableURLs = []byte("urls")
|
||||
)
|
||||
|
||||
// DB representation of a Store.
|
||||
// Only one table/bucket which contains the urls, so it's not a fully Database,
|
||||
// it works only with single bucket because that all we need.
|
||||
type DB struct {
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
var _ Store = &DB{}
|
||||
|
||||
// openDatabase open a new database connection
|
||||
// and returns its instance.
|
||||
func openDatabase(stumb string) *bolt.DB {
|
||||
// Open the data(base) file in the current working directory.
|
||||
// It will be created if it doesn't exist.
|
||||
db, err := bolt.Open(stumb, 0600, nil)
|
||||
if err != nil {
|
||||
Panic(err)
|
||||
}
|
||||
|
||||
// create the buckets here
|
||||
var tables = [...][]byte{
|
||||
tableURLs,
|
||||
}
|
||||
|
||||
db.Update(func(tx *bolt.Tx) (err error) {
|
||||
for _, table := range tables {
|
||||
_, err = tx.CreateBucketIfNotExists(table)
|
||||
if err != nil {
|
||||
Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
// NewDB returns a new DB instance, its connection is opened.
|
||||
// DB implements the Store.
|
||||
func NewDB(stumb string) *DB {
|
||||
return &DB{
|
||||
db: openDatabase(stumb),
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets a shorten url and its key
|
||||
// Note: Caller is responsible to generate a key.
|
||||
func (d *DB) Set(key string, value string) error {
|
||||
d.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(tableURLs)
|
||||
// Generate ID for the url
|
||||
// Note: we could use that instead of a random string key
|
||||
// but we want to simulate a real-world url shortener
|
||||
// so we skip that.
|
||||
// id, _ := b.NextSequence()
|
||||
return b.Put([]byte(key), []byte(value))
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns a url by its key.
|
||||
//
|
||||
// Returns an empty string if not found.
|
||||
func (d *DB) Get(key string) (value string) {
|
||||
keyb := []byte(key)
|
||||
d.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(tableURLs)
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
if bytes.Equal(keyb, k) {
|
||||
value = string(v)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Len returns all the "shorted" urls length
|
||||
func (d *DB) Len() (num int) {
|
||||
d.db.View(func(tx *bolt.Tx) error {
|
||||
|
||||
// Assume bucket exists and has keys
|
||||
b := tx.Bucket(tableURLs)
|
||||
|
||||
b.ForEach(func([]byte, []byte) error {
|
||||
num++
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Close the data(base) connection
|
||||
func (d *DB) Close() error {
|
||||
return d.db.Close()
|
||||
}
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | |
|
||||
// | Factory |
|
||||
// | |
|
||||
// +------------------------------------------------------------+
|
||||
|
||||
// Generator the type to generate keys(short urls) based on 'n'
|
||||
type Generator func(n int) string
|
||||
|
||||
// DefaultGenerator is the defautl url generator (the simple randomString)
|
||||
var DefaultGenerator = randomString
|
||||
|
||||
// Factory is responsible to generate keys(short urls)
|
||||
type Factory struct {
|
||||
store Store
|
||||
generator Generator
|
||||
}
|
||||
|
||||
// NewFactory receives a generator and a store and returns a new url Factory.
|
||||
func NewFactory(generator Generator, store Store) *Factory {
|
||||
return &Factory{
|
||||
store: store,
|
||||
generator: generator,
|
||||
}
|
||||
}
|
||||
|
||||
// Gen generates the key.
|
||||
func (f *Factory) Gen(uri string) (key string, err error) {
|
||||
// we don't return the parsed url because #hash are converted to uri-compatible
|
||||
// and we don't want to encode/decode all the time, there is no need for that,
|
||||
// we save the url as the user expects if the uri validation passed.
|
||||
_, err = url.ParseRequestURI(uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
key = f.generator(len(uri))
|
||||
// Make sure that the key is unique
|
||||
for {
|
||||
if v := f.store.Get(key); v == "" {
|
||||
break
|
||||
}
|
||||
key = f.generator((len(uri) / 2) + 1)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
const (
|
||||
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||
)
|
||||
|
||||
func randomString(n int) string {
|
||||
src := rand.NewSource(time.Now().UnixNano())
|
||||
b := make([]byte, n)
|
||||
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
||||
if remain == 0 {
|
||||
cache, remain = src.Int63(), letterIdxMax
|
||||
}
|
||||
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||
b[i] = letterBytes[idx]
|
||||
i--
|
||||
}
|
||||
cache >>= letterIdxBits
|
||||
remain--
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
body{
|
||||
background-color:silver;
|
||||
}
|
||||
4
_examples/examples/advanced/url-shortener/shortener.go
Normal file
4
_examples/examples/advanced/url-shortener/shortener.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package main
|
||||
|
||||
// Version is the current version of the url-shortener package.
|
||||
const Version = "0.0.1"
|
||||
@@ -0,0 +1,21 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Golang URL Shortener</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Golang URL Shortener</h2>
|
||||
<h3>{{ .form_result}}</h3>
|
||||
<form action="/shorten" method="POST">
|
||||
<input type="text" name="url" style="width: 35em;" />
|
||||
<input type="submit" value="Shorten!" />
|
||||
</form>
|
||||
{{ if isPositive .url_count }}
|
||||
<p>{{ .url_count }} URLs shortened</p>
|
||||
{{ end }}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user