Browse Source

Initial commit

Vova Tkach 6 years ago
parent
commit
07be74e92d

+ 12 - 0
.gitignore

@@ -10,3 +10,15 @@
 
 # Output of the go coverage tool, specifically when used with LiteIDE
 *.out
+
+# Binary files
+/fave
+/bin/*
+
+# Virtual hosts logs
+/hosts/*/logs/*
+!/hosts/*/logs/.*
+
+# Virtual hosts temp dir
+/hosts/*/tmp/*
+!/hosts/*/tmp/.*

+ 26 - 0
Makefile

@@ -0,0 +1,26 @@
+VERSION="1.0.0"
+
+default: debug
+
+debug:
+	@go build -o ./fave
+
+build: clean
+	@-mkdir ./bin
+	@cd ./bin
+	@cd ..
+	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o ./bin/fave.linux-amd64 -ldflags='-X main.Version=$(VERSION) -extldflags "-static"'
+	CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -a -o ./bin/fave.darwin-amd64 -ldflags='-X main.Version=$(VERSION) -extldflags "-static"'
+	CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -a -o ./bin/fave.windows-amd64.exe -ldflags='-X main.Version=$(VERSION) -extldflags "-static"'
+	cd ./bin && find . -name 'fave*' | xargs -I{} tar czf {}.tar.gz {}
+	cd ./bin && shasum -a 256 * > sha256sum.txt
+	cat ./bin/sha256sum.txt
+
+clean:
+	@-rm -r ./bin
+
+test:
+	@go test
+
+run:
+	@./fave -host 0.0.0.0 -port 8080 -dir ./hosts

+ 174 - 0
engine/sessions/sessions.go

@@ -0,0 +1,174 @@
+package sessions
+
+import (
+	"crypto/sha1"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math/rand"
+	"net/http"
+	"os"
+	"strconv"
+	"time"
+)
+
+type Vars struct {
+	FInt    map[string]int
+	FString map[string]string
+	FBool   map[string]bool
+}
+
+type Session struct {
+	w         *http.ResponseWriter
+	r         *http.Request
+	vhost     string
+	vhosthome string
+	remoteip  string
+	vars      *Vars
+	Ident     string
+}
+
+func New(w *http.ResponseWriter, r *http.Request, vhost string, vhosthome string, remoteip string) *Session {
+	return &Session{w, r, vhost, vhosthome, remoteip, &Vars{}, ""}
+}
+
+func (s *Session) Load() {
+	var session, err = s.r.Cookie("fsession")
+	if err == nil && len(session.Value) == 40 {
+		// Load session
+		s.Ident = session.Value
+		StartNewSession := true
+		fsessfile := s.vhosthome + "/tmp/" + s.Ident
+		file, ferr := os.Open(fsessfile)
+		if ferr == nil {
+			defer file.Close()
+			dec := json.NewDecoder(file)
+			ferr = dec.Decode(&s.vars)
+			if ferr == nil {
+				StartNewSession = false
+			}
+		}
+		if StartNewSession {
+			sessdata := Vars{}
+			sessdata.FInt = map[string]int{}
+			sessdata.FString = map[string]string{}
+			sessdata.FBool = map[string]bool{}
+			s.vars = &sessdata
+		}
+	} else {
+		// Create new session
+		// Generate unique hash
+		rand.Seed(time.Now().Unix())
+		rnd := rand.Intn(9999999-99) + 99
+		userstr := s.vhost + s.remoteip + s.r.Header.Get("User-Agent") +
+			strconv.FormatInt((int64(time.Now().Unix())), 10) +
+			strconv.FormatInt(int64(rnd), 10)
+		userhashstr := fmt.Sprintf("%x", sha1.Sum([]byte(userstr)))
+		s.Ident = userhashstr
+
+		// Try to create session file
+		sessdata := Vars{}
+		sessdata.FInt = map[string]int{}
+		sessdata.FString = map[string]string{}
+		sessdata.FBool = map[string]bool{}
+		s.vars = &sessdata
+
+		// Set session cookie
+		expiration := time.Now().Add(365 * 24 * time.Hour)
+		cookie := http.Cookie{Name: "fsession", Value: userhashstr, Expires: expiration}
+		http.SetCookie(*s.w, &cookie)
+	}
+}
+
+func (s *Session) Save() bool {
+	// TODO: Save session to file only if any var was modified
+	fsessfile := s.vhosthome + "/tmp/" + s.Ident
+	r, err := json.Marshal(s.vars)
+	if err == nil {
+		file, ferr := os.Create(fsessfile)
+		if ferr == nil {
+			defer file.Close()
+			_, ferr = file.WriteString(string(r))
+			return true
+		}
+	}
+	return false
+}
+
+func (s *Session) IsSetInt(name string) bool {
+	if _, ok := s.vars.FInt[name]; ok {
+		return true
+	} else {
+		return false
+	}
+}
+
+func (s *Session) IsSetString(name string) bool {
+	if _, ok := s.vars.FString[name]; ok {
+		return true
+	} else {
+		return false
+	}
+}
+
+func (s *Session) IsSetBool(name string) bool {
+	if _, ok := s.vars.FBool[name]; ok {
+		return true
+	} else {
+		return false
+	}
+}
+
+func (s *Session) SetInt(name string, value int) {
+	s.vars.FInt[name] = value
+}
+
+func (s *Session) SetString(name string, value string) {
+	s.vars.FString[name] = value
+}
+
+func (s *Session) SetBool(name string, value bool) {
+	s.vars.FBool[name] = value
+}
+
+func (s *Session) GetInt(name string) (int, error) {
+	if s.IsSetInt(name) {
+		return s.vars.FInt[name], nil
+	} else {
+		return 0, errors.New("Variable is not found")
+	}
+}
+
+func (s *Session) GetString(name string) (string, error) {
+	if s.IsSetString(name) {
+		return s.vars.FString[name], nil
+	} else {
+		return "", errors.New("Variable is not found")
+	}
+}
+
+func (s *Session) GetBool(name string) (bool, error) {
+	if s.IsSetBool(name) {
+		return s.vars.FBool[name], nil
+	} else {
+		return false, errors.New("Variable is not found")
+	}
+}
+
+func (s *Session) DelInt(name string) {
+	if s.IsSetInt(name) {
+		delete(s.vars.FInt, name)
+	}
+}
+
+func (s *Session) DelString(name string) {
+	if s.IsSetString(name) {
+		delete(s.vars.FString, name)
+	}
+}
+
+func (s *Session) DelBool(name string) {
+	if s.IsSetBool(name) {
+		delete(s.vars.FBool, name)
+	}
+}

+ 38 - 0
engine/wrapper/redirects.go

@@ -0,0 +1,38 @@
+package wrapper
+
+import (
+	"io/ioutil"
+	"net/http"
+	"strings"
+)
+
+func (e *Wrapper) redirectToMainDomain() bool {
+	file, err := ioutil.ReadFile(e.DirVhostHome + "/config/domain")
+	if err == nil {
+		maindomain := strings.TrimSpace(string(file))
+		port := ""
+		if e.Port != "80" {
+			port = ":" + e.Port
+		}
+		if maindomain+port != e.R.Host {
+			http.Redirect(*e.W, e.R, e.R.URL.Scheme+"://"+maindomain+
+				port+e.R.URL.RequestURI(), 301)
+			return true
+		}
+	}
+	return false
+}
+
+func (e *Wrapper) redirectSeoFix() bool {
+	full := e.R.URL.RequestURI()
+	uris := full[len(e.R.URL.Path):]
+	if len(e.R.URL.Path) > 0 {
+		if e.R.URL.Path[len(e.R.URL.Path)-1] != '/' {
+			http.Redirect(*e.W, e.R, e.R.URL.Path+"/"+uris, 301)
+			return true
+		}
+	} else {
+		return false
+	}
+	return false
+}

+ 3 - 0
engine/wrapper/resources/images/assets.sys.bg.png.go

@@ -0,0 +1,3 @@
+package images
+
+var File_assets_sys_bg_png = []byte{137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 30, 0, 0, 0, 30, 1, 3, 0, 0, 0, 1, 254, 60, 225, 0, 0, 0, 6, 80, 76, 84, 69, 255, 255, 255, 255, 255, 255, 85, 124, 245, 108, 0, 0, 0, 2, 116, 82, 78, 83, 13, 36, 255, 62, 87, 164, 0, 0, 0, 48, 73, 68, 65, 84, 8, 215, 99, 104, 80, 224, 96, 96, 112, 16, 96, 97, 96, 80, 224, 96, 98, 96, 16, 96, 97, 100, 96, 0, 50, 26, 24, 128, 12, 7, 6, 32, 67, 129, 1, 200, 16, 96, 128, 43, 164, 135, 14, 0, 115, 128, 11, 236, 193, 23, 245, 239, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130}

File diff suppressed because it is too large
+ 2 - 0
engine/wrapper/resources/images/assets.sys.logo.png.go


File diff suppressed because it is too large
+ 2 - 0
engine/wrapper/resources/others/assets.sys.fave.ico.go


+ 3 - 0
engine/wrapper/resources/others/assets.sys.logo.svg.go

@@ -0,0 +1,3 @@
+package others
+
+var File_assets_sys_logo_svg = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45" style="enable-background:new 0 0 45 45;" xml:space="preserve" version="1.1" id="svg2"><metadata id="metadata8"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><defs id="defs6"><clipPath id="clipPath16" clipPathUnits="userSpaceOnUse"><path id="path18" d="M 0,36 36,36 36,0 0,0 0,36 Z"/></clipPath></defs><g transform="matrix(1.25,0,0,-1.25,0,45)" id="g10"><g id="g12"><g clip-path="url(#clipPath16)" id="g14"><g transform="translate(35.8848,24.1665)" id="g20"><path id="path22" style="fill:#FFF;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c 0,5.45 -4.418,9.868 -9.867,9.868 -3.308,0 -6.227,-1.633 -8.018,-4.129 -1.79,2.496 -4.71,4.129 -8.017,4.129 -5.45,0 -9.868,-4.418 -9.868,-9.868 0,-0.772 0.098,-1.52 0.266,-2.241 1.371,-8.512 10.835,-17.494 17.619,-19.96 6.783,2.466 16.249,11.448 17.617,19.96 C -0.098,-1.52 0,-0.772 0,0"/></g></g></g></g></svg>`)

+ 114 - 0
engine/wrapper/resources/styles/assets.sys.styles.css.go

@@ -0,0 +1,114 @@
+package styles
+
+var File_assets_sys_styles_css = []byte(`html {
+    min-height: 100%;
+    height: 100%;
+    position: relative
+}
+
+body {
+    background: #205081 url(/assets/sys/bg.png) repeat 0 0;
+    margin: 0;
+    padding: 0;
+    color: #FFF;
+    font-family: Arial, sans-serif;
+    font-weight: 300;
+    font-size: 18px;
+    line-height: 21px;
+    position: relative;
+    width: 100%;
+    min-height: 100%;
+    height: 100%;
+    height: auto;
+    display: table
+}
+
+.wrapper {
+    padding: 15px;
+    text-align: center;
+    display: table-cell;
+    vertical-align: middle
+}
+
+.wrapper .logo {
+    width: 332px;
+    height: 203px;
+    margin: 0 auto;
+    background: url(/assets/sys/logo.png) no-repeat 0 0
+}
+
+.wrapper .logo .svg {
+    width: 150px;
+    height: 150px;
+    margin: 0 auto;
+    position: relative;
+    padding-top: 40px
+}
+
+.wrapper .logo .svg img {
+    top: 50%;
+    left: 50%;
+    position: absolute;
+    width: 150px;
+    height: 150px;
+    margin-top: -75px;
+    margin-left: -75px;
+    animation-name: fave;
+    animation-duration: 1000ms;
+    animation-iteration-count: infinite;
+    animation-timing-function: linear
+}
+
+@keyframes fave {
+    0% {
+        width: 150px;
+        height: 150px;
+        margin-top: -75px;
+        margin-left: -75px
+    }
+
+    40% {
+        width: 150px;
+        height: 150px;
+        margin-top: -75px;
+        margin-left: -75px
+    }
+
+    60% {
+        width: 120px;
+        height: 120px;
+        margin-top: -60px;
+        margin-left: -60px
+    }
+
+    100% {
+        width: 150px;
+        height: 150px;
+        margin-top: -75px;
+        margin-left: -75px
+    }
+}
+
+h1, h2 {
+    font-weight: 400;
+    font-size: 34px;
+    line-height: 36px;
+    margin: 10px 0
+}
+
+h2 {
+    font-weight: 350;
+    font-size: 14px;
+    line-height: 18px;
+    margin-bottom: 0
+}
+
+@media only screen and (max-width:800px) {
+    .wrapper {
+        padding: 15px 0
+    }
+
+    h1, h2 {
+        padding: 0 15px
+    }
+}`)

+ 25 - 0
engine/wrapper/resources/templates/page.default.go

@@ -0,0 +1,25 @@
+package templates
+
+var PageDefault = []byte(`<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<meta name="theme-color" content="#205081" />
+		<title>Default Page</title>
+		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+		<meta name="viewport" content="width=device-width, initial-scale=0.8, maximum-scale=0.8" />
+		<link rel="shortcut icon" href="/assets/sys/fave.ico" type="image/x-icon" />
+		<link rel="stylesheet" type="text/css" media="all" href="/assets/sys/styles.css" />
+	</head>
+	<body>
+		<div class="wrapper">
+			<div class="logo">
+				<div class="svg">
+					<img src="/assets/sys/logo.svg" width="150" height="150" />
+				</div>
+			</div>
+			<h1>Default Page</h1>
+			<h2><script>document.write(document.location.host);</script><noscript>fave.pro</noscript></h2>
+		</div>
+	</body>
+</html>`)

+ 25 - 0
engine/wrapper/resources/templates/page.error.404.go

@@ -0,0 +1,25 @@
+package templates
+
+var PageError404 = []byte(`<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<meta name="theme-color" content="#205081" />
+		<title>404 Not Found</title>
+		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+		<meta name="viewport" content="width=device-width, initial-scale=0.8, maximum-scale=0.8" />
+		<link rel="shortcut icon" href="/assets/sys/fave.ico" type="image/x-icon" />
+		<link rel="stylesheet" type="text/css" media="all" href="/assets/sys/styles.css" />
+	</head>
+	<body>
+		<div class="wrapper">
+			<div class="logo">
+				<div class="svg">
+					<img src="/assets/sys/logo.svg" width="150" height="150" />
+				</div>
+			</div>
+			<h1>404 Not Found</h1>
+			<h2><script>document.write(document.location.host);</script><noscript>fave.pro</noscript></h2>
+		</div>
+	</body>
+</html>`)

+ 113 - 0
engine/wrapper/static.go

@@ -0,0 +1,113 @@
+package wrapper
+
+import (
+	"io"
+	"net/http"
+	"os"
+	"strconv"
+	"strings"
+
+	Images "golang-fave/engine/wrapper/resources/images"
+	Others "golang-fave/engine/wrapper/resources/others"
+	Styles "golang-fave/engine/wrapper/resources/styles"
+	Templates "golang-fave/engine/wrapper/resources/templates"
+)
+
+func (e *Wrapper) isFileExists(file string) bool {
+	if fi, err := os.Stat(file); !os.IsNotExist(err) {
+		if err == nil {
+			fmode := fi.Mode()
+			if !fmode.IsDir() {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func (e *Wrapper) getFileContentType(file string, of *os.File) string {
+	fContentType := ""
+	if strings.HasSuffix(file, ".htm") {
+		fContentType = "text/html"
+	} else if strings.HasSuffix(file, ".html") {
+		fContentType = "text/html"
+	} else if strings.HasSuffix(file, ".txt") {
+		fContentType = "text/plain"
+	} else if strings.HasSuffix(file, ".php") {
+		fContentType = "text/plain"
+	} else if strings.HasSuffix(file, ".css") {
+		fContentType = "text/css"
+	} else if strings.HasSuffix(file, ".png") {
+		fContentType = "image/png"
+	} else if strings.HasSuffix(file, ".jpg") {
+		fContentType = "image/jpeg"
+	} else if strings.HasSuffix(file, ".jpeg") {
+		fContentType = "image/jpeg"
+	} else {
+		fContentType = "application/octet-stream"
+	}
+	return fContentType
+}
+
+func (e *Wrapper) staticResource() bool {
+	if e.R.URL.Path == "/assets/sys/styles.css" {
+		(*e.W).Header().Set("Content-Type", "text/css")
+		(*e.W).Write(Styles.File_assets_sys_styles_css)
+		return true
+	} else if e.R.URL.Path == "/assets/sys/logo.svg" {
+		(*e.W).Header().Set("Content-Type", "image/svg+xml")
+		(*e.W).Write(Others.File_assets_sys_logo_svg)
+		return true
+	} else if e.R.URL.Path == "/assets/sys/bg.png" {
+		(*e.W).Header().Set("Content-Type", "image/png")
+		(*e.W).Write(Images.File_assets_sys_bg_png)
+		return true
+	} else if e.R.URL.Path == "/assets/sys/logo.png" {
+		(*e.W).Header().Set("Content-Type", "image/png")
+		(*e.W).Write(Images.File_assets_sys_logo_png)
+		return true
+	} else if e.R.URL.Path == "/assets/sys/fave.ico" {
+		(*e.W).Header().Set("Content-Type", "image/x-icon")
+		(*e.W).Write(Others.File_assets_sys_fave_ico)
+		return true
+	}
+	return false
+}
+
+func (e *Wrapper) staticFile() bool {
+	file := e.R.URL.Path
+	if file == "/" {
+		if e.isFileExists(e.DirVhostHome + "/htdocs" + "/index.htm") {
+			file = "/index.htm"
+		} else if e.isFileExists(e.DirVhostHome + "/htdocs" + "/index.html") {
+			file = "/index.html"
+		}
+	}
+	if file != "/" {
+		if e.isFileExists(e.DirVhostHome + "/htdocs" + file) {
+			of, err := os.Open(e.DirVhostHome + "/htdocs" + file)
+			defer of.Close()
+			if err == nil {
+				fstat, _ := of.Stat()
+				fsize := strconv.FormatInt(fstat.Size(), 10)
+				(*e.W).Header().Add("Content-Type", e.getFileContentType(file, of))
+				(*e.W).Header().Add("Content-Length", fsize)
+				of.Seek(0, 0)
+				io.Copy(*e.W, of)
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func (e *Wrapper) printPageDefault() {
+	(*e.W).Header().Set("Content-Type", "text/html")
+	(*e.W).Write(Templates.PageDefault)
+}
+
+func (e *Wrapper) printPage404() {
+	(*e.W).WriteHeader(http.StatusNotFound)
+	(*e.W).Header().Set("Content-Type", "text/html")
+	(*e.W).Write(Templates.PageError404)
+}

+ 143 - 0
engine/wrapper/wrapper.go

@@ -0,0 +1,143 @@
+package wrapper
+
+import (
+	"log"
+	"net"
+	"net/http"
+	"os"
+	"strings"
+
+	"golang-fave/engine/sessions"
+)
+
+type Wrapper struct {
+	W            *http.ResponseWriter
+	R            *http.Request
+	VHost        string
+	Port         string
+	DirWww       string
+	DirVhostHome string
+	RemoteIp     string
+	LoggerAcc    *log.Logger
+	LoggerErr    *log.Logger
+	Session      *sessions.Session
+	Debug        bool
+}
+
+type handleRun func(e *Wrapper) bool
+
+func New(w *http.ResponseWriter, r *http.Request, vhost string, port string, wwwdir string, vhosthome string, debug bool) *Wrapper {
+	return &Wrapper{
+		VHost:        vhost,
+		Port:         port,
+		DirWww:       wwwdir,
+		DirVhostHome: vhosthome,
+		W:            w,
+		R:            r,
+		Debug:        debug,
+	}
+}
+
+func (e *Wrapper) Run(hr handleRun) {
+	// Populate some values
+	e.RemoteIp = e.R.RemoteAddr
+
+	// Create loggers
+	e.LoggerAcc = log.New(os.Stdout, e.VHost+", ", log.LstdFlags)
+	e.LoggerErr = log.New(os.Stdout, e.VHost+", ", log.LstdFlags)
+
+	// Attach file for access log
+	if !e.Debug {
+		acclogfile, acclogfileerr := os.OpenFile(e.DirVhostHome+"/logs/access.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
+		if acclogfileerr == nil {
+			defer acclogfile.Close()
+			e.LoggerAcc.SetOutput(acclogfile)
+		}
+	}
+
+	// Attach file for access log
+	if !e.Debug {
+		errlogfile, errlogfileerr := os.OpenFile(e.DirVhostHome+"/logs/error.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
+		if errlogfileerr == nil {
+			defer errlogfile.Close()
+			e.LoggerErr.SetOutput(errlogfile)
+		}
+	}
+
+	// Fix remote IP
+	if strings.ContainsRune(e.R.RemoteAddr, ':') {
+		e.RemoteIp, _, _ = net.SplitHostPort(e.R.RemoteAddr)
+	}
+
+	// Redirect to main domain
+	if e.redirectToMainDomain() {
+		e.Log("301")
+		return
+	}
+
+	// Static resource
+	if e.staticResource() {
+		e.Log("200")
+		return
+	}
+
+	// Static file
+	if e.staticFile() {
+		e.Log("200")
+		return
+	}
+
+	// Friendly search engine url
+	if e.redirectSeoFix() {
+		e.Log("301")
+		return
+	}
+
+	// Create and load session
+	e.Session = sessions.New(e.W, e.R, e.VHost, e.DirVhostHome, e.RemoteIp)
+	e.Session.Load()
+
+	// Set session vars
+	if !e.Session.IsSetInt("UserId") {
+		e.Session.SetInt("UserId", 0)
+	}
+	if !e.Session.IsSetBool("IsLogged") {
+		e.Session.SetBool("IsLogged", false)
+	}
+
+	// Logic
+	ret := false
+	if hr != nil {
+		if hr(e) {
+			ret = true
+		}
+	}
+
+	// Save session
+	e.Session.Save()
+
+	if ret {
+		return
+	}
+
+	// Show default page
+	if e.R.URL.Path == "/" {
+		e.Log("200")
+		e.printPageDefault()
+	} else {
+		e.LogError("404")
+		e.printPage404()
+	}
+}
+
+func (e *Wrapper) Log(value string) {
+	e.LoggerAcc.Println("[ACC] [" + e.R.Method + "] [" + value + "] [" + e.RemoteIp +
+		"] [" + e.R.URL.Scheme + "://" + e.R.Host + e.R.URL.RequestURI() +
+		"] [" + e.R.Header.Get("User-Agent") + "]")
+}
+
+func (e *Wrapper) LogError(value string) {
+	e.LoggerErr.Println("[ERR] [" + e.R.Method + "] [" + value + "] [" + e.RemoteIp +
+		"] [" + e.R.URL.Scheme + "://" + e.R.Host + e.R.URL.RequestURI() +
+		"] [" + e.R.Header.Get("User-Agent") + "]")
+}

+ 0 - 0
hosts/.keep


+ 0 - 0
hosts/localhost/.keep


+ 0 - 0
hosts/localhost/config/.keep


+ 0 - 0
hosts/localhost/htdocs/.keep


+ 2 - 0
hosts/localhost/htdocs/robots.txt

@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /

+ 2 - 0
hosts/localhost/htdocs/static.html

@@ -0,0 +1,2 @@
+Static Page<br />
+<a href="/">Main Page</a>

+ 0 - 0
hosts/localhost/logs/.keep


+ 0 - 0
hosts/localhost/template/.keep


+ 0 - 0
hosts/localhost/tmp/.keep


+ 110 - 0
main.go

@@ -0,0 +1,110 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"os"
+	"strconv"
+	"strings"
+
+	"golang-fave/engine/wrapper"
+)
+
+const C_Debug = false
+
+var FParamHost string
+var FParamPort int
+var FParamWwwDir string
+var FVhostHomeDir string
+
+func init() {
+	flag.StringVar(&FParamHost, "host", "0.0.0.0", "server host")
+	flag.IntVar(&FParamPort, "port", 8080, "server port")
+	flag.StringVar(&FParamWwwDir, "dir", "", "virtual hosts directory")
+	flag.Parse()
+}
+
+func main() {
+	if _, err := os.Stat(FParamWwwDir); os.IsNotExist(err) {
+		fmt.Println("Virtual hosts directory is not exists")
+		fmt.Println("Example: ./fave -host 127.0.0.1 -port 80 -dir ./hosts")
+		return
+	}
+	if FParamWwwDir[len(FParamWwwDir)-1] != '/' {
+		FParamWwwDir = FParamWwwDir + "/"
+	}
+	http.HandleFunc("/", handler)
+	fmt.Println("Starting server at " + FParamHost + ":" + strconv.Itoa(FParamPort))
+	log.Fatal(http.ListenAndServe(FParamHost+":"+strconv.Itoa(FParamPort), nil))
+}
+
+func vhExists(vhosthome string) bool {
+	if fi, err := os.Stat(vhosthome); !os.IsNotExist(err) {
+		if err == nil {
+			fmode := fi.Mode()
+			if fmode.IsDir() {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func handler(w http.ResponseWriter, r *http.Request) {
+	// Build vhost home dir
+	host := r.Host
+	port := strconv.Itoa(FParamPort)
+	index := strings.Index(host, ":")
+	if index > -1 {
+		port = host[index+1:]
+		host = host[0:index]
+	}
+
+	// Cut "www" if exists
+	if strings.HasPrefix(host, "www.") {
+		host = host[4:]
+	}
+	FVhostHomeDir = FParamWwwDir + host
+
+	// Check if virtual host exists
+	if !vhExists(FVhostHomeDir) {
+		host = "localhost"
+		FVhostHomeDir = FParamWwwDir + host
+	}
+
+	// Set protocol
+	r.URL.Scheme = "http"
+
+	// Set server name
+	w.Header().Set("Server", "fave.pro")
+
+	// Create and start engine
+	wrapper.New(&w, r, host, port, FParamWwwDir, FVhostHomeDir, C_Debug).
+		Run(func(e *wrapper.Wrapper) bool {
+			if e.R.URL.Path == "/" {
+				if !e.Session.IsSetInt("CounterTest") {
+					e.Session.SetInt("CounterTest", 1)
+				}
+
+				cc, err := e.Session.GetInt("CounterTest")
+				if err != nil {
+					cc = 1
+				}
+
+				(*e.W).Header().Set("Content-Type", "text/html")
+				io.WriteString(*e.W, "Home<br />")
+				io.WriteString(*e.W, "<a href=\"/static.html\">Static Page</a><br />")
+				io.WriteString(*e.W, "<a href=\"/robots.txt\">robots.txt</a><br />")
+				io.WriteString(*e.W, "<a href=\"/static404\">Page 404</a><br />")
+				io.WriteString(*e.W, "Counter: "+strconv.Itoa(cc))
+
+				e.Session.SetInt("CounterTest", cc+1)
+
+				return true
+			}
+			return false
+		})
+}

Some files were not shown because too many files changed in this diff