Skip to content

Commit

Permalink
Developed🔥
Browse files Browse the repository at this point in the history
  • Loading branch information
bisakhmondal committed Apr 20, 2021
1 parent d0d996b commit 1b9cb17
Show file tree
Hide file tree
Showing 14 changed files with 781 additions and 36 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.vscode
.idea
*.log
30 changes: 30 additions & 0 deletions conf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

Configuration:
info:
name: "web-serve" # Name of the server as a service
short: "The Production Build Server" # Short info about the server service name

server:
port: 8080 # Port on which server is running
mode: "RELEASE" # Another option DEBUG

failsafe: true # If failsafe is set for Single Page Application while hitting virtual routes index.html will be rendered alongside

htmlblacklist: # list of routes where html will not be rendered - a 404 page will be rendered
- /ping # health check
- /admin

blacklist: # list of routes where instead of an 404 HTML an 404 JSON will be returned
- /custom

page404: html/404.html # Path where 404.html can be customised for certain routes if not handled by the SPA

timeout: # in seconds
read: 1
write: 1
idle: 120

logging: # Each request is going to be logged
logdir: .
logfile: traffic.log
stdout: true # Also log to Stdout true/false
57 changes: 48 additions & 9 deletions core/cmd.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
// Package core
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package core

import (
"embed"
"fmt"
"github.com/spf13/cobra"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/spf13/cobra"
)

var service *Service
var (
service *Service
conf *Config
)

func CLICommand(content embed.FS) *cobra.Command {
func CLICommand(content embed.FS, config *Config) *cobra.Command {
conf = config
cobra.OnInitialize(func() {
var err error
service, err = createService(runner, content)
Expand Down Expand Up @@ -40,19 +61,37 @@ func CLICommand(content embed.FS) *cobra.Command {
}

func runner() error {
router, err := routeConfig(service.fs)
writer, closer := CombinedWriter()
defer closer()
handler, err := routeConfig(service.fs, writer)
if err != nil {
return err
}
go router.Run(":8080")
addr := fmt.Sprintf(":%d", conf.Configuration.Server.Port)

sig := make(chan os.Signal, 1)
server := http.Server{
Addr: addr,
Handler: handler,
ReadTimeout: DurationCast(conf.Configuration.Server.Timeout.Read, time.Second),
WriteTimeout: DurationCast(conf.Configuration.Server.Timeout.Write, time.Second),
IdleTimeout: DurationCast(conf.Configuration.Server.Timeout.IDLE, time.Second),
}

go func() {
err := server.ListenAndServe()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to run the server: %s\n", err)
os.Exit(1)
}
}()

signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
quit := make(chan os.Signal, 1)

sis := <-sig
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
defer signal.Stop(quit)
sig := <-quit

fmt.Printf("signal received: %s. exiting... ", sis.String())
fmt.Printf("signal received: %s. exiting... ", sig.String())
return nil
}

Expand Down
1 change: 1 addition & 0 deletions core/logger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package core
38 changes: 38 additions & 0 deletions core/logging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Package core
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package core

import (
"fmt"
"github.com/gin-gonic/gin"
"time"
)

func logFilter() gin.HandlerFunc {
return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return fmt.Sprintf("IP %s - [%s] \"%s PATH: %s \t\t%s %d LATENCY: %s\" %s\" %s\" BODYSIZE: %d\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
param.BodySize,
)
})
}
76 changes: 76 additions & 0 deletions core/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Package core
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package core

import (
"bytes"
"fmt"
"github.com/spf13/viper"
"os"
)

type Info struct {
Name string
Short string
}

type Server struct {
Port int
Mode string
Failsafe bool
HtmlBlackList []string
Blacklist []string
Page404 string
Timeout struct {
Read int
Write int
IDLE int
}
}
type Logging struct {
Logdir string
Logfile string
Stdout bool
}

type Config struct {
Configuration struct {
Info Info
Server Server
Logging Logging
}
}

func (c *Config) Parse(conf string) error {
viper.SetConfigType("yml")
err := viper.ReadConfig(bytes.NewBuffer([]byte(conf)))

if err != nil {
panic(err)
}

err = viper.Unmarshal(c)
if err != nil {
return fmt.Errorf("failed to parse configuration YAML: %s\n", err)
}
if c.Configuration.Logging.Logdir == "." {
dir, err := os.Getwd()
if err != nil {
return fmt.Errorf("use path instead of '.' for logdir: %s", err)
}
c.Configuration.Logging.Logdir = dir
}
return nil
}
69 changes: 47 additions & 22 deletions core/router.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
// Package core
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package core

import (
"embed"
"github.com/gin-gonic/gin"
"io"
"io/fs"
"net/http"
"os"
"path/filepath"
"strings"
)

var HTMLBlackListRoutes = []string{
"/ping",
"/admin",
}
var NoCustomisedRoutes = []string{
"/custom",
}

func routeConfig(efs embed.FS) (*gin.Engine, error) {
func routeConfig(efs embed.FS, w io.Writer) (*gin.Engine, error) {
if conf.Configuration.Server.Mode == "DEBUG" {
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
f, _ := os.OpenFile(filepath.Join(conf.Configuration.Logging.Logdir,
conf.Configuration.Logging.Logfile),
os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
router := gin.New()
//logger
router.Use(logFilter())
router.Use(gin.Recovery())
filesys := fs.FS(efs)
subtree, err := fs.Sub(filesys, "html")
if err != nil {
Expand All @@ -26,11 +47,12 @@ func routeConfig(efs embed.FS) (*gin.Engine, error) {
router.GET("/ping", func(g *gin.Context) {
g.String(http.StatusOK, "pong")
})
router.Use(noTouch())
router.Use(blacklistJSON())
router.Use(serve("/", subtree))
router.NoRoute(func(g *gin.Context) {
if g.Request.Method == "GET" {
g.FileFromFS("404.html", http.FS(subtree))
g.FileFromFS(strings.TrimPrefix(conf.Configuration.Server.Page404, "html/"),
http.FS(subtree))
g.Abort()
} else {
g.String(http.StatusNotFound, "404 method not allowed")
Expand All @@ -46,10 +68,14 @@ type spaFileSystem struct {

func (fs *spaFileSystem) Open(name string) (http.File, error) {
f, err := fs.FileSystem.Open(name)
//Default failsafe page
if err != nil {
return fs.FileSystem.Open("build/index.html")

if conf.Configuration.Server.Failsafe {
//Default failsafe page
if err != nil {
return fs.FileSystem.Open("build/index.html")
}
}

return f, err
}

Expand All @@ -59,16 +85,16 @@ func serve(urlPrefix string, fss fs.FS) gin.HandlerFunc {
fileserver = http.StripPrefix(urlPrefix, fileserver)
}
return func(c *gin.Context) {
if c.Request.Method == "GET" && !isBlacklisted(c.Request.URL.Path) {
if c.Request.Method == "GET" && (exists(urlPrefix, c.Request.URL.Path, &fss) || !blacklistHTML(c.Request.URL.Path)) {
fileserver.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
}
}

func noTouch() gin.HandlerFunc {
func blacklistJSON() gin.HandlerFunc {
return func(g *gin.Context) {
if noCustomised(g.Request.URL.Path) {
if noHTMLCustomised(g.Request.URL.Path) {
g.JSON(http.StatusNotFound,
gin.H{
"code": "PAGE_NOT_FOUND",
Expand All @@ -77,21 +103,20 @@ func noTouch() gin.HandlerFunc {
g.Abort()
}
g.Next()

}
}

func isBlacklisted(path string) bool {
for _, checkpath := range HTMLBlackListRoutes {
func blacklistHTML(path string) bool {
for _, checkpath := range conf.Configuration.Server.HtmlBlackList {
if strings.HasPrefix(path, checkpath) {
return true
}
}
return false
}

func noCustomised(path string) bool {
for _, checkpath := range NoCustomisedRoutes {
func noHTMLCustomised(path string) bool {
for _, checkpath := range conf.Configuration.Server.Blacklist {
if strings.HasPrefix(path, checkpath) {
return true
}
Expand Down
Loading

0 comments on commit 1b9cb17

Please sign in to comment.