Esempio n. 1
1
func Example_handler() {
	c := xhandler.Chain{}

	host, _ := os.Hostname()
	conf := xlog.Config{
		// Set some global env fields
		Fields: xlog.F{
			"role": "my-service",
			"host": host,
		},
	}

	// Install the logger handler with default output on the console
	c.UseC(xlog.NewHandler(conf))

	// Plug the xlog handler's input to Go's default logger
	log.SetFlags(0)
	log.SetOutput(xlog.New(conf))

	// Install some provided extra handler to set some request's context fields.
	// Thanks to those handler, all our logs will come with some pre-populated fields.
	c.UseC(xlog.RemoteAddrHandler("ip"))
	c.UseC(xlog.UserAgentHandler("user_agent"))
	c.UseC(xlog.RefererHandler("referer"))
	c.UseC(xlog.RequestIDHandler("req_id", "Request-Id"))

	// Here is your final handler
	h := c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		// Get the logger from the context. You can safely assume it will be always there,
		// if the handler is removed, xlog.FromContext will return a NopLogger
		l := xlog.FromContext(ctx)

		// Then log some errors
		if err := errors.New("some error from elsewhere"); err != nil {
			l.Errorf("Here is an error: %v", err)
		}

		// Or some info with fields
		l.Info("Something happend", xlog.F{
			"user":   "******",
			"status": "ok",
		})
	}))
	http.Handle("/", h)

	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.SetOutput(os.Stderr) // make sure we print to console
		log.Fatal(err)
	}
}
Esempio n. 2
0
func ExampleNewHandler() {
	c := xhandler.Chain{}

	// Install the metric handler with dogstatsd backend client and some env tags
	flushInterval := 5 * time.Second
	tags := []string{"role:my-service"}
	statsdWriter, err := net.Dial("udp", "127.0.0.1:8126")
	if err != nil {
		log.Fatal(err)
	}
	c.UseC(xstats.NewHandler(dogstatsd.New(statsdWriter, flushInterval), tags))

	// Here is your handler
	h := c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		// Get the xstats request's instance from the context. You can safely assume it will
		// be always there, if the handler is removed, xstats.FromContext will return a nop
		// instance.
		m := xstats.FromContext(ctx)

		// Count something
		m.Count("requests", 1, "route:index")
	}))

	http.Handle("/", h)

	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}
Esempio n. 3
0
func main() {
	// Create a REST API resource index
	index := resource.NewIndex()

	// Add a resource on /users[/:user_id]
	users := index.Bind("users", resource.New(user, mem.NewHandler(), resource.Conf{
		// We allow all REST methods
		// (rest.ReadWrite is a shortcut for []resource.Mode{resource.Create, resource.Read, resource.Update, resource.Delete, resource,List})
		AllowedModes: resource.ReadWrite,
	}))

	// Bind a sub resource on /users/:user_id/posts[/:post_id]
	// and reference the user on each post using the "user" field of the posts resource.
	posts := users.Bind("posts", "user", resource.New(post, mem.NewHandler(), resource.Conf{
		// Posts can only be read, created and deleted, not updated
		AllowedModes: []resource.Mode{resource.Read, resource.List, resource.Create, resource.Delete},
	}))

	// Add a friendly alias to public posts
	// (equivalent to /users/:user_id/posts?filter={"published":true})
	posts.Alias("public", url.Values{"filter": []string{"{\"published\":true}"}})

	// Create API HTTP handler for the resource graph
	api, err := rest.NewHandler(index)
	if err != nil {
		log.Fatalf("Invalid API configuration: %s", err)
	}

	// Init a xhandler chain (see https://github.com/rs/xhandler)
	c := xhandler.Chain{}

	// Add close notifier handler so context is cancelled when the client closes
	// the connection
	c.UseC(xhandler.CloseHandler)

	// Add timeout handler
	c.UseC(xhandler.TimeoutHandler(2 * time.Second))

	// Install a logger (see https://github.com/rs/xlog)
	c.UseC(xlog.NewHandler(xlog.Config{}))

	// Log API access
	c.UseC(xaccess.NewHandler())

	// Add CORS support with passthrough option on so rest-layer can still
	// handle OPTIONS method
	c.UseC(cors.New(cors.Options{OptionsPassthrough: true}).HandlerC)

	// Bind the API under /api/ path
	http.Handle("/api/", http.StripPrefix("/api/", c.Handler(api)))

	// Serve it
	log.Print("Serving API on http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}
Esempio n. 4
0
func main() {
	c := xhandler.Chain{}

	// Use default options
	c.UseC(cors.Default().HandlerC)

	mux := http.NewServeMux()
	mux.Handle("/", c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"hello\": \"world\"}"))
	})))

	http.ListenAndServe(":8080", mux)
}
func ExampleIf() {
	c := xhandler.Chain{}

	// Add a timeout handler only if the URL path matches a prefix
	c.UseC(xhandler.If(
		func(ctx context.Context, w http.ResponseWriter, r *http.Request) bool {
			return strings.HasPrefix(r.URL.Path, "/with-timeout/")
		},
		xhandler.TimeoutHandler(2*time.Second),
	))

	http.Handle("/", c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
		fmt.Fprintf(w, "Welcome to the home page!")
	})))
}
Esempio n. 6
0
func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	env, err := getEnviron()
	if err != nil {
		log.Error(err)
		os.Exit(1)
	}

	l, err := log.ParseLevel(env.logLevel)
	if err != nil {
		l = log.ErrorLevel
	}
	log.SetLevel(l)

	log.Infof("Service %s started", serviceID)

	log.Infof("%s=%s", tmpDirEnvar, env.tmpDir)
	log.Infof("%s=%s", authServerEnvar, env.authServer)
	log.Infof("%s=%s", dataServerEnvar, env.dataServer)
	log.Infof("%s=%s", metaServerEnvar, env.metaServer)
	log.Infof("%s=%d\n", portEnvar, env.port)
	log.Infof("%s=%s\n", sharedSecretEnvar, "******")

	p := &newServerParams{}
	p.authServer = env.authServer
	p.dataServer = env.dataServer
	p.metaServer = env.metaServer
	p.sharedSecret = env.sharedSecret
	p.tmpDir = env.tmpDir

	// Create chunk tmp dir
	if err := os.MkdirAll(p.tmpDir, 0644); err != nil {
		log.Error(err)
		os.Exit(1)
	}

	srv, err := newServer(p)
	if err != nil {
		log.Error(err)
		os.Exit(1)
	}

	c := xhandler.Chain{}
	c.UseC(xhandler.CloseHandler)

	http.Handle(endPoint, c.Handler(srv))
	log.Error(http.ListenAndServe(fmt.Sprintf(":%d", env.port), nil))
}
Esempio n. 7
0
func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	c := xhandler.Chain{}
	c.UseC(xhandler.CloseHandler)

	env, err := getEnviron()
	if err != nil {
		log.Error(err)
		os.Exit(1)
	}

	l, err := log.ParseLevel(env.logLevel)
	if err != nil {
		l = log.ErrorLevel
	}
	log.SetLevel(l)

	log.Infof("Service %s started", serviceID)

	printEnviron(env)

	p := &newServerParams{}
	p.dataDir = env.dataDir
	p.tmpDir = env.tmpDir
	p.checksum = env.checksum
	p.prop = env.prop
	p.sharedSecret = env.sharedSecret

	// Create data and tmp dirs
	if err := os.MkdirAll(p.dataDir, 0644); err != nil {
		log.Error(err)
		os.Exit(1)
	}
	if err := os.MkdirAll(p.tmpDir, 0644); err != nil {
		log.Error(err)
		os.Exit(1)
	}

	srv, err := newServer(p)
	if err != nil {
		log.Error(err)
		os.Exit(1)
	}

	http.Handle(endPoint, c.Handler(srv))
	log.Error(http.ListenAndServe(fmt.Sprintf(":%d", env.port), nil))
}
Esempio n. 8
0
func ExampleMux() {
	c := xhandler.Chain{}

	// Append a context-aware middleware handler
	c.UseC(xhandler.CloseHandler)

	// Another context-aware middleware handler
	c.UseC(xhandler.TimeoutHandler(2 * time.Second))

	mux := xmux.New()

	// Use c.Handler to terminate the chain with your final handler
	mux.GET("/welcome/:name", xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
		fmt.Fprintf(w, "Welcome %s!", xmux.Param(ctx, "name"))
	}))

	if err := http.ListenAndServe(":8080", c.Handler(mux)); err != nil {
		log.Fatal(err)
	}
}
Esempio n. 9
0
func ExampleChain() {
	c := xhandler.Chain{}
	// Append a context-aware middleware handler
	c.UseC(xhandler.CloseHandler)

	// Mix it with a non-context-aware middleware handler
	c.Use(cors.Default().Handler)

	// Another context-aware middleware handler
	c.UseC(xhandler.TimeoutHandler(2 * time.Second))

	mux := http.NewServeMux()

	// Use c.Handler to terminate the chain with your final handler
	mux.Handle("/", c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
		fmt.Fprintf(w, "Welcome to the home page!")
	})))

	// You can reuse the same chain for other handlers
	mux.Handle("/api", c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
		fmt.Fprintf(w, "Welcome to the API!")
	})))
}
Esempio n. 10
0
// setupHandlerChain does plumbing for logging and such.
func setupHandlerChain(c *xhandler.Chain) {
	host, _ := os.Hostname()
	conf := xlog.Config{
		// Log info level and higher
		Level: xlog.LevelInfo,
		// Set some global env fields
		Fields: xlog.F{
			"role": "my-shitty-service",
			"host": host,
		},
		// Output everything on console
		Output: xlog.NewOutputChannel(xlog.NewConsoleOutput()),
	}

	// Add close notifier handler so context is cancelled when the client closes
	// the connection
	c.UseC(xhandler.CloseHandler)

	// Install the logger handler
	c.UseC(xlog.NewHandler(conf))

	// Add timeout handler (HAHA)
	//c.UseC(xhandler.TimeoutHandler(2 * time.Second))

	// Install some provided extra handler to set some request's context fields.
	// Thanks to those handler, all our logs will come with some pre-populated fields.
	c.UseC(xlog.MethodHandler("method"))
	c.UseC(xlog.URLHandler("url"))
	c.UseC(xlog.RemoteAddrHandler("ip"))
	c.UseC(xlog.UserAgentHandler("user_agent"))
	c.UseC(xlog.RefererHandler("referer"))
	c.UseC(xlog.RequestIDHandler("req_id", "Request-Id"))
}
Esempio n. 11
0
File: main.go Progetto: blang/posty
func main() {
	if !checkFlags() {
		os.Exit(1)
	}

	sessionStore := sessions.NewCookieStore([]byte(*sessionHashKey), []byte(*sessionBlockKey))

	// OpenID Connect Providers

	// Google
	oidcGoogleLoginRoute := "/logingoogle"
	oidcGoogleCBRoute := "/gcallback"
	oidcGoogle := &oidc.Google{
		ClientID:     *oidcGoogleClientID,
		ClientSecret: *oidcGoogleClientSecret,
		RedirectURI:  *publicURL + oidcGoogleCBRoute,
		SessionStore: sessionStore,
	}

	// PayPal
	oidcPaypalLoginRoute := "/loginpaypal"
	oidcPaypalCBRoute := "/pcallback"
	oidcPaypal := &oidc.Paypal{
		ClientID:     *oidcPaypalClientID,
		ClientSecret: *oidcPaypalClientSecret,
		RedirectURI:  *publicURL + oidcPaypalCBRoute,
		SessionStore: sessionStore,
	}

	// Dynamodb
	cfg := &aws.Config{}
	if *dynamodbEndpoint != "" {
		cfg.Endpoint = aws.String(*dynamodbEndpoint)
	}
	sess := session.New(cfg)
	if *debug {
		sess.Config.LogLevel = aws.LogLevel(aws.LogDebug)
	}

	// Model
	var m model.Model
	m = awsdynamo.NewModelFromSession(sess)

	// Controller
	// OAuth / OpenID Connect
	authCGoogle := controller.NewAuthController(m.UserPeer(), oidcGoogle, "google")
	authCPaypal := controller.NewAuthController(m.UserPeer(), oidcPaypal, "paypal")

	// Post Controller
	postContrData := &postDataProvider{
		PostPeer: m.PostPeer(),
		UserPeer: m.UserPeer(),
	}
	postController := &controller.PostController{
		Model: postContrData,
	}

	// Middleware
	baseChain := xhandler.Chain{}
	baseChain.UseC(xhandler.TimeoutHandler(2 * time.Second))

	// Session management
	sessionMiddleware := middleware.Session{}
	sessionMiddleware.Init([]byte(*sessionHashKey), []byte(*sessionBlockKey))
	baseChain.UseC(sessionMiddleware.Enable("posty-session"))

	// Chain for authenticated routes
	authedChain := xhandler.Chain{}
	authedChain = append(authedChain, baseChain...)
	authedChain.UseC(middleware.AuthenticatedFilter("/login"))
	authedChain.UseC(middleware.UserContext())

	// Chain for authenticated routes with json response
	jsonChain := xhandler.Chain{}
	jsonChain = append(jsonChain, authedChain...)
	jsonChain.UseC(middleware.JSONWrapper())

	// Chain for unauthenticated routes
	unauthedChain := xhandler.Chain{}
	unauthedChain = append(unauthedChain, baseChain...)
	unauthedChain.UseC(middleware.UnauthenticatedFilter("/"))

	// Main Context
	ctx := context.Background()
	route := func(chain xhandler.Chain, handler xhandler.HandlerC) web.Handler {
		return handle(ctx, chain.HandlerC(handler))
	}

	// Routes
	mux := web.New()
	mux.Get("/api/posts", route(jsonChain, xhandler.HandlerFuncC(postController.Posts)))
	mux.Post("/api/posts", route(jsonChain, xhandler.HandlerFuncC(postController.Create)))
	mux.Delete("/api/posts/:id", route(jsonChain, xhandler.HandlerFuncC(postController.Remove)))
	// OIDC Routes
	mux.Get(oidcGoogleLoginRoute, route(unauthedChain, authCGoogle.Login()))
	mux.Get(oidcGoogleCBRoute, route(unauthedChain, authCGoogle.Callback("/")))
	mux.Get(oidcPaypalLoginRoute, route(unauthedChain, authCPaypal.Login()))
	mux.Get(oidcPaypalCBRoute, route(unauthedChain, authCPaypal.Callback("/")))
	mux.Get("/logout", route(authedChain, authCGoogle.Logout("/login")))

	// Static file
	mux.Get("/login", route(unauthedChain, serveSingleFile(filepath.Join(*frontendPath, "login.html"))))
	mux.Get("/", route(authedChain, serveSingleFile(filepath.Join(*frontendPath, "index.html"))))
	mux.Get("/static/*", route(baseChain, serveFiles(filepath.Join(*frontendPath, "/static"), "/static/")))

	log.Infof("Listening on %s", *listen)
	log.Fatal(http.ListenAndServe(":8080", gctx.ClearHandler(mux)))
}
Esempio n. 12
0
func construct(o *webOptions) xhandler.HandlerC {
	var chain xhandler.Chain
	chain.UseC(xhandler.CloseHandler)
	chain.UseC(xhandler.TimeoutHandler(o.reqTimeout))
	chain.UseC(o.componentSetter)
	chain.UseC(o.templateCtxSetter)
	for _, m := range o.middlewares {
		chain.UseC(m)
	}
	chain.UseC(CompileInContext)
	chain.UseC(RenderInContext)
	if o.alwaysHTML {
		return chain.HandlerCF(WriteRenderedHTML)
	}
	return chain.HandlerCF(WriteRendered)
}
Esempio n. 13
0
func Example() {
	var (
		// Define a user resource schema
		user = schema.Schema{
			"id": schema.Field{
				Required: true,
				// When a field is read-only, on default values or hooks can
				// set their value. The client can't change it.
				ReadOnly: true,
				// This is a field hook called when a new user is created.
				// The schema.NewID hook is a provided hook to generate a
				// unique id when no value is provided.
				OnInit: &schema.NewID,
				// The Filterable and Sortable allows usage of filter and sort
				// on this field in requests.
				Filterable: true,
				Sortable:   true,
				Validator: &schema.String{
					Regexp: "^[0-9a-f]{32}$",
				},
			},
			"created": schema.Field{
				Required:   true,
				ReadOnly:   true,
				Filterable: true,
				Sortable:   true,
				OnInit:     &schema.Now,
				Validator:  &schema.Time{},
			},
			"updated": schema.Field{
				Required:   true,
				ReadOnly:   true,
				Filterable: true,
				Sortable:   true,
				OnInit:     &schema.Now,
				// The OnUpdate hook is called when the item is edited. Here we use
				// provided Now hook which just return the current time.
				OnUpdate:  &schema.Now,
				Validator: &schema.Time{},
			},
			// Define a name field as required with a string validator
			"name": schema.Field{
				Required:   true,
				Filterable: true,
				Validator: &schema.String{
					MaxLen: 150,
				},
			},
		}

		// Define a post resource schema
		post = schema.Schema{
			// schema.*Field are shortcuts for common fields (identical to users' same fields)
			"id":      schema.IDField,
			"created": schema.CreatedField,
			"updated": schema.UpdatedField,
			// Define a user field which references the user owning the post.
			// See bellow, the content of this field is enforced by the fact
			// that posts is a sub-resource of users.
			"user": schema.Field{
				Required:   true,
				Filterable: true,
				Validator: &schema.Reference{
					Path: "users",
				},
			},
			"public": schema.Field{
				Filterable: true,
				Validator:  &schema.Bool{},
			},
			// Sub-documents are handled via a sub-schema
			"meta": schema.Field{
				Schema: &schema.Schema{
					"title": schema.Field{
						Required: true,
						Validator: &schema.String{
							MaxLen: 150,
						},
					},
					"body": schema.Field{
						Validator: &schema.String{
							MaxLen: 100000,
						},
					},
				},
			},
		}
	)

	// Create a REST API root resource
	index := resource.NewIndex()

	// Add a resource on /users[/:user_id]
	users := index.Bind("users", resource.New(user, mem.NewHandler(), resource.Conf{
		// We allow all REST methods
		// (rest.ReadWrite is a shortcut for []rest.Mode{Create, Read, Update, Delete, List})
		AllowedModes: resource.ReadWrite,
	}))

	// Bind a sub resource on /users/:user_id/posts[/:post_id]
	// and reference the user on each post using the "user" field of the posts resource.
	posts := users.Bind("posts", "user", resource.New(post, mem.NewHandler(), resource.Conf{
		// Posts can only be read, created and deleted, not updated
		AllowedModes: []resource.Mode{resource.Read, resource.List, resource.Create, resource.Delete},
	}))

	// Add a friendly alias to public posts
	// (equivalent to /users/:user_id/posts?filter={"public":true})
	posts.Alias("public", url.Values{"filter": []string{"{\"public\"=true}"}})

	// Create API HTTP handler for the resource graph
	api, err := rest.NewHandler(index)
	if err != nil {
		log.Fatalf("Invalid API configuration: %s", err)
	}

	// Init a xhandler chain (see https://github.com/rs/xhandler)
	c := xhandler.Chain{}

	// Add close notifier handler so context is cancelled when the client closes
	// the connection
	c.UseC(xhandler.CloseHandler)

	// Add timeout handler
	c.UseC(xhandler.TimeoutHandler(2 * time.Second))

	// Install a logger (see https://github.com/rs/xlog)
	c.UseC(xlog.NewHandler(xlog.Config{}))

	// Log API access
	c.UseC(xaccess.NewHandler())

	// Add CORS support with passthrough option on so rest-layer can still
	// handle OPTIONS method
	c.UseC(cors.New(cors.Options{OptionsPassthrough: true}).HandlerC)

	// Bind the API under /api/ path
	http.Handle("/api/", http.StripPrefix("/api/", c.Handler(api)))

	// Serve it
	log.Print("Serving API on http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}
Esempio n. 14
0
func main() {
	auth.Config(*domain, *port, *client, *secret)
	tpl := templates.New("templates")

	// chain authenticated middleware
	c := xhandler.Chain{}
	c.UseC(func(next xhandler.HandlerC) xhandler.HandlerC {
		return auth.NewMiddleware(next)
	})

	// server static assets files
	fs := http.FileServer(http.Dir("assets"))
	http.Handle("/assets/", http.StripPrefix("/assets/", fs))

	http.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
		code := r.URL.Query().Get("code")
		token, err := auth.GetToken(code)
		if err != nil {
			http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		} else {
			id, err := db.CreateUser(token)
			if err != nil {
				tpl.Error(w, err)
			} else {
				auth.SaveSession(w, id)
				http.Redirect(w, r, "/entries/new", http.StatusFound)
			}
		}
	})

	http.Handle("/entries/", c.Handler(
		xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
			u := ctx.Value("user").(*user.User)

			switch r.Method {
			case "GET":
				switch r.URL.Path[len("/entries/"):] {
				case "":
					entries, err := db.FindEntries(u.ID, 200)
					if err != nil {
						tpl.Error(w, err)
					} else {
						tpl.Render(w, "entries", entries)
					}
				case "new":
					tpl.Render(w, "new_entry", u)
				default:
					tpl.NotFound(w)
				}
			case "POST":
				rate := r.FormValue("rate")
				desc := r.FormValue("description")
				_, err := db.CreateEntry(u.ID, rate, desc)
				if err != nil {
					tpl.Error(w, err)
				} else {
					http.Redirect(w, r, "/entries", http.StatusFound)
				}
			default:
				http.Error(w, "", http.StatusMethodNotAllowed)
			}
		})))

	http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
		auth.DestroySession(w)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
	})

	http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) {
		tpl.Render(w, "about", nil)
	})

	http.HandleFunc("/demo", func(w http.ResponseWriter, r *http.Request) {
		auth.SaveSession(w, demo_id)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
	})

	http.Handle("/stats/", c.Handler(
		xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
			u := ctx.Value("user").(*user.User)

			switch r.Method {
			case "GET":
				entries, err := db.FindEntries(u.ID, 10)
				if err == nil {
					p := struct {
						Distribution map[string]int
						Rate         entry.RateStats
					}{
						entry.FeelingsDistribution(entries),
						entry.RateByDay(entries),
					}
					if len(entries) == 0 {
						tpl.Render(w, "entries", entries)
						return
					}
					json, err := json.Marshal(p)
					if err == nil {
						tpl.Render(w, "stats", template.JS(json))
						return
					}
				}
				tpl.Error(w, err)
			default:
				http.Error(w, "", http.StatusMethodNotAllowed)
			}
		})))

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "GET" {
			if r.URL.Path != "/" {
				tpl.NotFound(w)
				return
			}
			_, err := auth.CurrenUser(r)
			if err == nil {
				http.Redirect(w, r, "/entries/new", 302)
			} else {
				p := struct {
					FacebookURL string
				}{
					auth.RedirectURL(),
				}
				tpl.Render(w, "index", p)
			}
		} else {
			http.Error(w, "", http.StatusMethodNotAllowed)
		}
	})

	log.Fatal(http.ListenAndServe(":"+*port, nil))
}