2020-07-28 19:17:07 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2021-07-17 07:33:44 +00:00
|
|
|
"database/sql"
|
|
|
|
"errors"
|
2020-12-13 14:16:47 +00:00
|
|
|
"fmt"
|
2021-03-10 17:08:20 +00:00
|
|
|
"log"
|
2021-09-23 16:24:45 +00:00
|
|
|
"net"
|
2020-07-28 19:17:07 +00:00
|
|
|
"net/http"
|
2022-08-09 15:25:22 +00:00
|
|
|
"sort"
|
2020-07-28 19:17:07 +00:00
|
|
|
"strconv"
|
2021-03-31 07:29:52 +00:00
|
|
|
"time"
|
2020-09-25 17:23:01 +00:00
|
|
|
|
2021-01-23 16:24:47 +00:00
|
|
|
"github.com/dchest/captcha"
|
2021-03-03 17:19:55 +00:00
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/go-chi/chi/v5/middleware"
|
2021-07-17 07:33:44 +00:00
|
|
|
"github.com/justinas/alice"
|
2022-02-16 18:37:56 +00:00
|
|
|
"github.com/klauspost/compress/flate"
|
2023-01-22 20:26:21 +00:00
|
|
|
"github.com/samber/lo"
|
2022-02-18 15:35:53 +00:00
|
|
|
"go.goblog.app/app/pkgs/httpcompress"
|
2021-08-02 18:43:24 +00:00
|
|
|
"go.goblog.app/app/pkgs/maprouter"
|
2022-08-09 15:25:22 +00:00
|
|
|
"go.goblog.app/app/pkgs/plugintypes"
|
2021-04-03 13:39:43 +00:00
|
|
|
"golang.org/x/net/context"
|
2020-07-28 19:17:07 +00:00
|
|
|
)
|
|
|
|
|
2020-10-26 16:37:31 +00:00
|
|
|
const (
|
2021-06-18 12:32:03 +00:00
|
|
|
contentType = "Content-Type"
|
2020-11-16 17:34:29 +00:00
|
|
|
userAgent = "User-Agent"
|
|
|
|
appUserAgent = "GoBlog"
|
2021-07-27 10:51:08 +00:00
|
|
|
|
|
|
|
blogKey contextKey = "blog"
|
|
|
|
pathKey contextKey = "httpPath"
|
2020-10-26 16:37:31 +00:00
|
|
|
)
|
|
|
|
|
2021-06-06 12:39:42 +00:00
|
|
|
func (a *goBlog) startServer() (err error) {
|
2021-06-17 14:34:57 +00:00
|
|
|
log.Println("Start server(s)...")
|
2021-07-17 07:33:44 +00:00
|
|
|
// Load router
|
2022-07-16 19:09:43 +00:00
|
|
|
a.reloadRouter()
|
2021-03-10 17:47:56 +00:00
|
|
|
// Set basic middlewares
|
2021-07-24 11:35:26 +00:00
|
|
|
h := alice.New()
|
2022-02-21 21:02:33 +00:00
|
|
|
h = h.Append(middleware.Heartbeat("/ping"))
|
2021-06-06 12:39:42 +00:00
|
|
|
if a.cfg.Server.Logging {
|
2021-07-24 11:35:26 +00:00
|
|
|
h = h.Append(a.logMiddleware)
|
|
|
|
}
|
2022-02-21 21:02:33 +00:00
|
|
|
h = h.Append(middleware.Recoverer, httpcompress.Compress(flate.BestCompression))
|
2022-10-30 19:57:25 +00:00
|
|
|
if a.cfg.Server.SecurityHeaders {
|
2021-07-24 11:35:26 +00:00
|
|
|
h = h.Append(a.securityHeaders)
|
2021-03-10 17:47:56 +00:00
|
|
|
}
|
2022-08-09 15:25:22 +00:00
|
|
|
// Add plugin middlewares
|
2023-01-22 20:26:21 +00:00
|
|
|
middlewarePlugins := lo.Map(a.getPlugins(pluginMiddlewareType), func(item any, index int) plugintypes.Middleware { return item.(plugintypes.Middleware) })
|
2022-08-09 15:25:22 +00:00
|
|
|
sort.Slice(middlewarePlugins, func(i, j int) bool {
|
|
|
|
// Sort with descending prio
|
|
|
|
return middlewarePlugins[i].Prio() > middlewarePlugins[j].Prio()
|
|
|
|
})
|
|
|
|
for _, plugin := range middlewarePlugins {
|
|
|
|
h = h.Append(plugin.Handler)
|
|
|
|
}
|
|
|
|
// Finally...
|
2022-07-16 19:09:43 +00:00
|
|
|
finalHandler := h.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
a.d.ServeHTTP(w, r)
|
|
|
|
})
|
2021-03-19 09:10:47 +00:00
|
|
|
// Start Onion service
|
2021-06-06 12:39:42 +00:00
|
|
|
if a.cfg.Server.Tor {
|
2021-03-19 09:10:47 +00:00
|
|
|
go func() {
|
2021-06-06 12:39:42 +00:00
|
|
|
if err := a.startOnionService(finalHandler); err != nil {
|
2021-04-03 13:39:43 +00:00
|
|
|
log.Println("Tor failed:", err.Error())
|
|
|
|
}
|
2021-03-19 09:10:47 +00:00
|
|
|
}()
|
|
|
|
}
|
2021-04-02 08:28:04 +00:00
|
|
|
// Start server
|
2022-10-30 19:57:25 +00:00
|
|
|
if a.cfg.Server.HttpsRedirect {
|
2021-04-02 08:28:04 +00:00
|
|
|
go func() {
|
2021-10-13 07:01:54 +00:00
|
|
|
// Start HTTP server for redirects
|
2022-04-12 06:48:09 +00:00
|
|
|
h := http.Handler(http.HandlerFunc(a.redirectToHttps))
|
|
|
|
if m := a.getAutocertManager(); m != nil {
|
|
|
|
h = m.HTTPHandler(h)
|
|
|
|
}
|
2021-10-13 07:01:54 +00:00
|
|
|
httpServer := &http.Server{
|
2022-08-05 07:18:46 +00:00
|
|
|
Addr: ":80",
|
|
|
|
Handler: h,
|
|
|
|
ReadHeaderTimeout: 1 * time.Minute,
|
|
|
|
ReadTimeout: 5 * time.Minute,
|
|
|
|
WriteTimeout: 5 * time.Minute,
|
2021-10-13 07:01:54 +00:00
|
|
|
}
|
|
|
|
a.shutdown.Add(shutdownServer(httpServer, "http server"))
|
2022-10-30 19:57:25 +00:00
|
|
|
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
2021-04-02 08:28:04 +00:00
|
|
|
log.Println("Failed to start HTTP server:", err.Error())
|
|
|
|
}
|
|
|
|
}()
|
2022-10-30 19:57:25 +00:00
|
|
|
}
|
|
|
|
s := &http.Server{
|
|
|
|
Handler: finalHandler,
|
|
|
|
ReadHeaderTimeout: 1 * time.Minute,
|
|
|
|
ReadTimeout: 5 * time.Minute,
|
|
|
|
WriteTimeout: 5 * time.Minute,
|
|
|
|
}
|
|
|
|
a.shutdown.Add(shutdownServer(s, "main server"))
|
|
|
|
s.Addr = ":" + strconv.Itoa(a.cfg.Server.Port)
|
|
|
|
if a.cfg.Server.PublicHTTPS {
|
|
|
|
err = s.Serve(a.getAutocertManager().Listener())
|
|
|
|
} else if a.cfg.Server.manualHttps {
|
|
|
|
err = s.ListenAndServeTLS(a.cfg.Server.HttpsCert, a.cfg.Server.HttpsKey)
|
2021-04-02 08:28:04 +00:00
|
|
|
} else {
|
2022-10-30 19:57:25 +00:00
|
|
|
err = s.ListenAndServe()
|
|
|
|
}
|
|
|
|
if err == http.ErrServerClosed {
|
|
|
|
return nil
|
2020-07-29 14:41:36 +00:00
|
|
|
}
|
2022-10-30 19:57:25 +00:00
|
|
|
return err
|
2020-08-01 15:49:46 +00:00
|
|
|
}
|
2020-07-29 14:41:36 +00:00
|
|
|
|
2021-05-07 14:14:15 +00:00
|
|
|
func shutdownServer(s *http.Server, name string) func() {
|
|
|
|
return func() {
|
|
|
|
toc, c := context.WithTimeout(context.Background(), 5*time.Second)
|
2021-06-17 14:34:57 +00:00
|
|
|
defer c()
|
|
|
|
if err := s.Shutdown(toc); err != nil {
|
|
|
|
log.Printf("Error on server shutdown (%v): %v", name, err)
|
|
|
|
}
|
2021-05-07 14:14:15 +00:00
|
|
|
log.Println("Stopped server:", name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-23 16:24:45 +00:00
|
|
|
func (*goBlog) redirectToHttps(w http.ResponseWriter, r *http.Request) {
|
|
|
|
requestHost, _, err := net.SplitHostPort(r.Host)
|
|
|
|
if err != nil {
|
|
|
|
requestHost = r.Host
|
|
|
|
}
|
2021-04-02 08:28:04 +00:00
|
|
|
w.Header().Set("Connection", "close")
|
2021-09-23 16:24:45 +00:00
|
|
|
http.Redirect(w, r, fmt.Sprintf("https://%s%s", requestHost, r.URL.RequestURI()), http.StatusMovedPermanently)
|
2021-04-02 08:28:04 +00:00
|
|
|
}
|
|
|
|
|
2021-03-19 12:04:11 +00:00
|
|
|
const (
|
|
|
|
paginationPath = "/page/{page:[0-9-]+}"
|
2022-08-07 11:11:43 +00:00
|
|
|
feedPath = ".{feed:(rss|json|atom|min\\.rss|min\\.json|min\\.atom)}"
|
2021-03-19 12:04:11 +00:00
|
|
|
)
|
2021-01-30 18:37:26 +00:00
|
|
|
|
2022-07-16 19:09:43 +00:00
|
|
|
func (a *goBlog) reloadRouter() {
|
|
|
|
a.d = a.buildRouter()
|
|
|
|
}
|
|
|
|
|
2022-02-22 09:14:48 +00:00
|
|
|
func (a *goBlog) buildRouter() http.Handler {
|
2021-08-02 18:43:24 +00:00
|
|
|
mapRouter := &maprouter.MapRouter{
|
|
|
|
Handlers: map[string]http.Handler{},
|
|
|
|
}
|
|
|
|
if shn := a.cfg.Server.shortPublicHostname; shn != "" {
|
|
|
|
mapRouter.Handlers[shn] = http.HandlerFunc(a.redirectShortDomain)
|
|
|
|
}
|
|
|
|
if mhn := a.cfg.Server.mediaHostname; mhn != "" && !a.isPrivate() {
|
|
|
|
mr := chi.NewMux()
|
|
|
|
|
|
|
|
mr.Use(middleware.RedirectSlashes)
|
|
|
|
mr.Use(middleware.CleanPath)
|
|
|
|
|
|
|
|
mr.Group(a.mediaFilesRouter)
|
|
|
|
|
|
|
|
mapRouter.Handlers[mhn] = mr
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default router
|
2021-07-27 10:51:08 +00:00
|
|
|
r := chi.NewMux()
|
2021-03-19 12:04:11 +00:00
|
|
|
|
2021-02-27 07:31:06 +00:00
|
|
|
// Basic middleware
|
2021-07-27 10:51:08 +00:00
|
|
|
r.Use(fixHTTPHandler)
|
2020-11-05 16:06:42 +00:00
|
|
|
r.Use(middleware.RedirectSlashes)
|
2020-12-22 19:29:25 +00:00
|
|
|
r.Use(middleware.CleanPath)
|
2021-08-02 19:10:38 +00:00
|
|
|
|
|
|
|
// Tor
|
|
|
|
if a.cfg.Server.Tor {
|
|
|
|
r.Use(a.addOnionLocation)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cache
|
2021-07-27 10:51:08 +00:00
|
|
|
if cache := a.cfg.Cache; cache != nil && !cache.Enable {
|
2020-10-26 16:37:31 +00:00
|
|
|
r.Use(middleware.NoCache)
|
|
|
|
}
|
2021-02-27 07:31:06 +00:00
|
|
|
|
|
|
|
// No Index Header
|
2021-07-27 10:51:08 +00:00
|
|
|
if a.isPrivate() {
|
2021-02-27 07:31:06 +00:00
|
|
|
r.Use(noIndexHeader)
|
|
|
|
}
|
|
|
|
|
2021-07-24 11:35:26 +00:00
|
|
|
// Login and captcha middleware
|
2021-06-06 12:39:42 +00:00
|
|
|
r.Use(a.checkIsLogin)
|
|
|
|
r.Use(a.checkIsCaptcha)
|
2020-07-29 15:17:48 +00:00
|
|
|
|
2021-07-27 10:51:08 +00:00
|
|
|
// Login
|
|
|
|
r.Group(a.loginRouter)
|
2021-02-24 15:01:10 +00:00
|
|
|
|
2020-10-06 17:07:48 +00:00
|
|
|
// Micropub
|
2021-07-27 10:51:08 +00:00
|
|
|
r.Route(micropubPath, a.micropubRouter)
|
2020-10-06 17:07:48 +00:00
|
|
|
|
2020-10-13 19:35:39 +00:00
|
|
|
// IndieAuth
|
2022-06-07 19:55:45 +00:00
|
|
|
r.Group(a.indieAuthRouter)
|
2020-10-13 19:35:39 +00:00
|
|
|
|
2020-10-26 16:37:31 +00:00
|
|
|
// ActivityPub and stuff
|
2021-07-27 10:51:08 +00:00
|
|
|
r.Group(a.activityPubRouter)
|
2020-10-26 16:37:31 +00:00
|
|
|
|
2020-11-06 17:45:31 +00:00
|
|
|
// Webmentions
|
2021-07-27 10:51:08 +00:00
|
|
|
r.Route(webmentionPath, a.webmentionsRouter)
|
2021-07-14 13:44:57 +00:00
|
|
|
|
2021-07-17 07:33:44 +00:00
|
|
|
// Notifications
|
2021-07-27 10:51:08 +00:00
|
|
|
r.Route(notificationsPath, a.notificationsRouter)
|
2020-07-29 15:17:48 +00:00
|
|
|
|
2020-09-19 10:27:07 +00:00
|
|
|
// Assets
|
2021-07-27 10:51:08 +00:00
|
|
|
r.Group(a.assetsRouter)
|
2020-09-19 10:27:07 +00:00
|
|
|
|
2020-12-23 13:11:14 +00:00
|
|
|
// Static files
|
2021-07-27 10:51:08 +00:00
|
|
|
r.Group(a.staticFilesRouter)
|
2020-12-23 13:11:14 +00:00
|
|
|
|
2021-01-10 14:59:43 +00:00
|
|
|
// Media files
|
2021-08-02 18:43:24 +00:00
|
|
|
r.Route("/m", a.mediaFilesRouter)
|
2021-01-10 14:59:43 +00:00
|
|
|
|
2022-11-27 14:06:43 +00:00
|
|
|
// Profile image
|
|
|
|
r.Group(a.profileImageRouter)
|
|
|
|
|
2021-11-10 10:13:30 +00:00
|
|
|
// Other routes
|
2021-12-12 10:03:14 +00:00
|
|
|
r.Route("/-", a.otherRoutesRouter)
|
2021-11-10 10:13:30 +00:00
|
|
|
|
2021-01-23 16:24:47 +00:00
|
|
|
// Captcha
|
2021-07-17 07:33:44 +00:00
|
|
|
r.Handle("/captcha/*", captcha.Server(500, 250))
|
2021-01-23 16:24:47 +00:00
|
|
|
|
2021-07-27 10:51:08 +00:00
|
|
|
// Blogs
|
2021-06-06 12:39:42 +00:00
|
|
|
for blog, blogConfig := range a.cfg.Blogs {
|
2021-07-27 10:51:08 +00:00
|
|
|
r.Group(a.blogRouter(blog, blogConfig))
|
2020-08-05 17:14:10 +00:00
|
|
|
}
|
|
|
|
|
2020-09-22 15:08:34 +00:00
|
|
|
// Sitemap
|
2021-07-27 10:51:08 +00:00
|
|
|
r.With(a.privateModeHandler, cacheLoggedIn, a.cacheMiddleware).Get(sitemapPath, a.serveSitemap)
|
2020-09-22 15:08:34 +00:00
|
|
|
|
2022-01-24 08:43:06 +00:00
|
|
|
// IndexNow
|
|
|
|
if a.indexNowEnabled() {
|
2022-02-25 15:29:42 +00:00
|
|
|
if inkey := a.indexNowKey(); len(inkey) > 0 {
|
|
|
|
r.With(cacheLoggedIn, a.cacheMiddleware).Get("/"+string(inkey)+".txt", a.serveIndexNow)
|
2022-01-24 08:43:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-27 10:51:08 +00:00
|
|
|
// Robots.txt
|
|
|
|
r.With(cacheLoggedIn, a.cacheMiddleware).Get(robotsTXTPath, a.serveRobotsTXT)
|
2020-11-09 15:40:12 +00:00
|
|
|
|
2021-11-22 16:05:49 +00:00
|
|
|
// Favicon
|
|
|
|
if !hasStaticPath("favicon.ico") {
|
2021-11-22 16:23:39 +00:00
|
|
|
r.With(a.cacheMiddleware).Get("/favicon.ico", a.serve404)
|
2021-11-22 16:05:49 +00:00
|
|
|
}
|
|
|
|
|
2021-07-27 10:51:08 +00:00
|
|
|
r.NotFound(a.servePostsAliasesRedirects())
|
2020-07-31 19:02:47 +00:00
|
|
|
|
2021-06-06 12:39:42 +00:00
|
|
|
r.MethodNotAllowed(a.serveNotAllowed)
|
2020-12-24 09:09:34 +00:00
|
|
|
|
2021-08-02 18:43:24 +00:00
|
|
|
mapRouter.DefaultHandler = r
|
2022-02-22 09:14:48 +00:00
|
|
|
return alice.New(headAsGetHandler).Then(mapRouter)
|
2020-07-28 19:17:07 +00:00
|
|
|
}
|
|
|
|
|
2021-07-27 10:51:08 +00:00
|
|
|
func (a *goBlog) servePostsAliasesRedirects() http.HandlerFunc {
|
2021-07-17 07:33:44 +00:00
|
|
|
// Private mode
|
2021-07-27 10:51:08 +00:00
|
|
|
alicePrivate := alice.New(a.privateModeHandler)
|
2021-07-17 07:33:44 +00:00
|
|
|
// Return handler func
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// Only allow GET requests
|
|
|
|
if r.Method != http.MethodGet {
|
|
|
|
a.serveNotAllowed(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Check if post or alias
|
|
|
|
path := r.URL.Path
|
2022-08-09 15:25:22 +00:00
|
|
|
row, err := a.db.QueryRow(`
|
2021-11-09 09:31:02 +00:00
|
|
|
-- normal posts
|
2022-09-23 09:05:07 +00:00
|
|
|
select 'post', status, visibility, 200 from posts where path = @path
|
2021-11-09 09:31:02 +00:00
|
|
|
union all
|
|
|
|
-- short paths
|
2022-09-23 09:05:07 +00:00
|
|
|
select 'alias', path, '', 301 from shortpath where printf('/s/%x', id) = @path
|
2021-07-17 07:33:44 +00:00
|
|
|
union all
|
2021-11-09 09:31:02 +00:00
|
|
|
-- post aliases
|
2022-09-23 09:05:07 +00:00
|
|
|
select 'alias', path, '', 302 from post_parameters where parameter = 'aliases' and value = @path
|
2021-07-23 07:53:35 +00:00
|
|
|
union all
|
2021-11-09 09:31:02 +00:00
|
|
|
-- deleted posts
|
2022-09-23 09:05:07 +00:00
|
|
|
select 'deleted', '', '', 410 from deleted where path = @path
|
2021-11-09 09:31:02 +00:00
|
|
|
-- just select the first result
|
2021-07-17 07:33:44 +00:00
|
|
|
limit 1
|
|
|
|
`, sql.Named("path", path))
|
|
|
|
if err != nil {
|
|
|
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2022-09-23 09:05:07 +00:00
|
|
|
var pathType, value1, value2 string
|
2021-11-09 09:31:02 +00:00
|
|
|
var status int
|
2022-09-23 09:05:07 +00:00
|
|
|
err = row.Scan(&pathType, &value1, &value2, &status)
|
2021-07-17 07:33:44 +00:00
|
|
|
if err != nil {
|
|
|
|
if !errors.Is(err, sql.ErrNoRows) {
|
|
|
|
// Error
|
|
|
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// No result, continue...
|
|
|
|
} else {
|
|
|
|
// Found post or alias
|
2021-11-09 09:31:02 +00:00
|
|
|
switch pathType {
|
2021-07-17 07:33:44 +00:00
|
|
|
case "post":
|
2022-09-23 09:05:07 +00:00
|
|
|
// Check status
|
|
|
|
switch postStatus(value1) {
|
|
|
|
case statusPublished:
|
|
|
|
// Check visibility
|
|
|
|
switch postVisibility(value2) {
|
|
|
|
case visibilityPublic, visibilityUnlisted:
|
|
|
|
alicePrivate.Append(a.checkActivityStreamsRequest, a.cacheMiddleware).ThenFunc(a.servePost).ServeHTTP(w, r)
|
|
|
|
default: // private, etc.
|
|
|
|
alice.New(a.authMiddleware).ThenFunc(a.servePost).ServeHTTP(w, r)
|
|
|
|
}
|
2021-07-17 07:33:44 +00:00
|
|
|
return
|
2022-09-23 09:05:07 +00:00
|
|
|
case statusPublishedDeleted:
|
2022-01-03 12:55:44 +00:00
|
|
|
if a.isLoggedIn(r) {
|
|
|
|
a.servePost(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
alicePrivate.Append(a.cacheMiddleware).ThenFunc(a.serve410).ServeHTTP(w, r)
|
|
|
|
return
|
2022-09-23 09:05:07 +00:00
|
|
|
default: // draft, scheduled, etc.
|
2021-07-17 07:33:44 +00:00
|
|
|
alice.New(a.authMiddleware).ThenFunc(a.servePost).ServeHTTP(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case "alias":
|
|
|
|
// Is alias, redirect
|
2021-07-25 08:53:12 +00:00
|
|
|
alicePrivate.Append(cacheLoggedIn, a.cacheMiddleware).ThenFunc(func(w http.ResponseWriter, r *http.Request) {
|
2022-09-23 09:05:07 +00:00
|
|
|
http.Redirect(w, r, value1, status)
|
2021-07-17 07:33:44 +00:00
|
|
|
}).ServeHTTP(w, r)
|
|
|
|
return
|
2021-07-23 07:53:35 +00:00
|
|
|
case "deleted":
|
|
|
|
// Is deleted, serve 410
|
2022-01-03 12:55:44 +00:00
|
|
|
alicePrivate.Append(a.cacheMiddleware).ThenFunc(a.serve410).ServeHTTP(w, r)
|
2021-07-23 07:53:35 +00:00
|
|
|
return
|
2021-07-17 07:33:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// No post, check regex redirects or serve 404 error
|
2021-07-24 11:35:26 +00:00
|
|
|
alice.New(a.cacheMiddleware, a.checkRegexRedirects).ThenFunc(a.serve404).ServeHTTP(w, r)
|
2021-07-17 07:33:44 +00:00
|
|
|
}
|
|
|
|
}
|
2021-11-19 16:36:03 +00:00
|
|
|
|
|
|
|
func (a *goBlog) getAppRouter() http.Handler {
|
|
|
|
for {
|
|
|
|
// Wait until router is ready
|
|
|
|
if a.d != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(time.Millisecond * 100)
|
|
|
|
}
|
|
|
|
return a.d
|
|
|
|
}
|