finished public server functionality and api to control it

This commit is contained in:
George Shaw 2017-11-07 20:07:23 -06:00
parent ca7e6416f9
commit 6e28648ce9
18 changed files with 410 additions and 54 deletions

View file

@ -0,0 +1,16 @@
jQuery('._js_public-server-maintenance-mode').on('click', function(e){
e.preventDefault();
var xhr = new XMLHttpRequest();
xhr.open('UPDATE', 'api/server/maintenance', true);
xhr.send();
xhr.onloadend = function() {
if(xhr.status == 204) {
getPublicServerStatus();
}
else {
alert('An error has occurred, please contact your webhost administrator.');
}
}
});

View file

@ -0,0 +1,30 @@
jQuery('._js_public-server-restart-graceful, ._js_public-server-restart-forceful').on('click', function(e){
e.preventDefault();
var requestData = {};
if(jQuery(this).hasClass('_js_public-server-restart-graceful')) {
requestData["graceful"] = true;
}
else {
requestData["graceful"] = false;
}
var xhr = new XMLHttpRequest();
xhr.open('UPDATE', 'api/server/restart', true);
xhr.send(JSON.stringify(requestData));
xhr.onloadend = function() {
if(xhr.status == 204) {
getPublicServerStatus();
alert('Server successfully restarted.');
}
else {
if(xhr.response) {
alert(xhr.response);
}
else {
alert('An error has occurred, please contact your webhost administrator.');
}
}
}
});

View file

@ -0,0 +1,29 @@
jQuery('._js_public-server-shutdown-graceful, ._js_public-server-shutdown-forceful').on('click', function(e){
e.preventDefault();
var requestData = {};
if(jQuery(this).hasClass('_js_public-server-shutdown-graceful')) {
requestData["graceful"] = true;
}
else {
requestData["graceful"] = false;
}
var xhr = new XMLHttpRequest();
xhr.open('UPDATE', 'api/server/shutdown', true);
xhr.send(JSON.stringify(requestData));
xhr.onloadend = function() {
if(xhr.status == 204) {
getPublicServerStatus();
}
else {
if(xhr.response) {
alert(xhr.response);
}
else {
alert('An error has occurred, please contact your webhost administrator.');
}
}
}
});

View file

@ -0,0 +1,21 @@
jQuery('._js_public-server-start').on('click', function(e){
e.preventDefault();
var xhr = new XMLHttpRequest();
xhr.open('UPDATE', 'api/server/start', true);
xhr.send();
xhr.onloadend = function() {
if(xhr.status == 204) {
getPublicServerStatus();
}
else {
if(xhr.response) {
alert(xhr.response);
}
else {
alert('An error has occurred, please contact your webhost administrator.');
}
}
}
});

View file

@ -0,0 +1,38 @@
var statusSpan = jQuery("._js_public-server-status");
function getPublicServerStatus() {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'api/server/status', true);
xhr.send();
xhr.onloadend = function() {
if(xhr.status == 200) {
statusSpan.attr('class', '_js_public-server-status');
switch(parseInt(xhr.response)) {
case 0:
statusSpan.addClass('text-danger').html("OFF");
break;
case 1:
statusSpan.addClass('text-success').html("ON");
break;
case 2:
statusSpan.addClass('text-warning').html("MAINTENANCE");
break;
case 3:
statusSpan.addClass('text-info').html("RESTARTING");
break;
default:
console.log(xhr.response);
statusSpan.addClass('text-danger').html('ERROR');
break;
}
}
else {
statusSpan.attr('class', '_js_public-server-status').addClass('text-danger').html('ERROR');
}
}
}
// Run it once on load
getPublicServerStatus();

View file

@ -21,7 +21,7 @@
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-primary my-2 my-sm-0" type="submit">Search</button>
</form>
<form id="logoutForm" method="POST" action="user_logout" class="form-inline mt-2 mt-md-0 ml-3">
<form id="logoutForm" method="POST" action="api/user/logout" class="form-inline mt-2 mt-md-0 ml-3">
<button class="btn btn-primary my-2 my-sm-0" type="submit">Logout</button>
</form>
</div>
@ -34,13 +34,15 @@
<div class="card-body">
<h4 class="card-title">Public Server</h4>
<h6 class="card-subtitle mb-4 text-muted">Handle operations for the public serving server</h6>
<button type="button" class="btn btn-outline-primary">Maintenance Mode</button>
<button type="button" class="btn btn-outline-primary">Graceful Shutdown</button>
<button type="button" class="btn btn-outline-primary">Forced Shutdown</button>
<button type="button" class="btn btn-outline-primary">Restart</button>
<button type="button" class="_js_public-server-start btn btn-outline-primary">Start</button>
<button type="button" class="_js_public-server-maintenance-mode btn btn-outline-primary">Maintenance Mode</button>
<button type="button" class="btn btn-outline-primary _js_public-server-shutdown-graceful">Graceful Shutdown</button>
<button type="button" class="btn btn-outline-primary _js_public-server-shutdown-forceful">Forced Shutdown</button>
<button type="button" class="btn btn-outline-primary _js_public-server-restart-graceful">Graceful Restart</button>
<button type="button" class="btn btn-outline-primary _js_public-server-restart-forceful">Forced Restart</button>
</div>
<div class="card-footer text-muted text-center">
Server Status: <span class="text-success">ON</span>
Server Status: <span class="_js_public-server-status text-muted">LOADING</span>
</div>
</div>
</div>
@ -64,8 +66,15 @@
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
<script type="text/javascript" src="assets/js/formHandlers/search.js"></script>
<script type="text/javascript" src="assets/js/formHandlers/logout.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/publicServer/status.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/publicServer/maintenance.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/publicServer/start.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/publicServer/shutdown.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/publicServer/restart.js"></script>
<!-- KEEP AT BOTTOM OF BODY TAGS -->
</body>
</html>

View file

@ -32,7 +32,7 @@
</div>
<div class="col-12 d-flex justify-content-center align-items-center">
<form id="loginForm" method="POST" action="user_auth">
<form id="loginForm" method="POST" action="api/user/auth">
<div class="form-group">
<label class="sr-only" for="loginUsername">Username</label>
<div class="input-group mb-2">
@ -52,7 +52,7 @@
</div>
<div class="col-12 d-flex justify-content-center align-items-center mt-5">
<form id="registerForm" method="POST" action="user_register">
<form id="registerForm" method="POST" action="api/user/register">
<div class="form-group">
<label class="sr-only" for="loginUsername">Username</label>
<div class="input-group mb-2">

12
main.go
View file

@ -4,22 +4,20 @@ import (
"log"
"net/http"
"github.com/Ennovar/gPanel/pkg/public"
"github.com/Ennovar/gPanel/pkg/webhost"
"github.com/gorilla/context"
)
func main() {
webhost := webhost.NewPrivateHost()
public := public.NewPublicWeb()
webhost := webhost.New()
log.Printf("To Exit: CTRL+C")
go func() {
log.Print("Listening (private) on localhost:2082, serving out of the document_roots/webhost/ directory...")
http.ListenAndServe("localhost:2082", context.ClearHandler(&webhost))
log.Print("Listening (public) on localhost:3000, serving out of the document_roots/public/ directory...")
_ = webhost.Public.Start()
}()
log.Print("Listening (public) on localhost:3000, serving out of the document_roots/public/ directory...")
http.ListenAndServe("localhost:3000", &public)
log.Print("Listening (private) on localhost:2082, serving out of the document_roots/webhost/ directory...")
http.ListenAndServe("localhost:2082", context.ClearHandler(&webhost))
}

View file

@ -5,23 +5,40 @@ import (
"net/http"
"strings"
"github.com/Ennovar/gPanel/pkg/api/server"
"github.com/Ennovar/gPanel/pkg/api/user"
"github.com/Ennovar/gPanel/pkg/public"
)
// HandleAPI function takes a path and determines if it is an API call, if it is it will
// call the specified API. It returns two booleans, the first being if it is an API call and
// the second is the response of the API call's function.
func HandleAPI(path string, res http.ResponseWriter, req *http.Request) (bool, bool) {
splitUrl := strings.Split(path, "/")
//
// The path of an API is formatted as such: document_roots/webhost/api/some/thing. The path is split
// by the sequence of characters "api", returning the first half of the URL which is discarded, and the
// second half which will look like /some/thing. The second half is processed to see whether or not it is
// a valid API, and then handled accordingly from there.
func HandleAPI(res http.ResponseWriter, req *http.Request, path string, publicServer *public.Controller) (bool, bool) {
splitUrl := strings.SplitN(path, "api", 2)
suspectApi := strings.ToLower(splitUrl[len(splitUrl)-1])
switch suspectApi {
case "api/user/auth":
case "/user/auth":
return true, user.Auth(res, req)
case "api/user/register":
case "/user/register":
return true, user.Register(res, req)
case "api/user/logout":
case "/user/logout":
return true, user.Logout(res, req)
case "/server/status":
return true, server.Status(res, req, publicServer)
case "/server/start":
return true, server.Start(res, req, publicServer)
case "/server/shutdown":
return true, server.Shutdown(res, req, publicServer)
case "/server/restart":
return true, server.Restart(res, req, publicServer)
case "/server/maintenance":
return true, server.Maintenance(res, req, publicServer)
default:
return false, false
}

View file

@ -1,8 +1,20 @@
// Package server is a child of package api to handle api calls concerning the server
package server
import "net/http"
import (
"net/http"
"github.com/Ennovar/gPanel/pkg/public"
)
func Maintenance(res http.ResponseWriter, req *http.Request, publicServer *public.Controller) bool {
if req.Method != "UPDATE" {
http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return false
}
publicServer.Maintenance()
res.WriteHeader(http.StatusNoContent)
func Maintenance(res http.ResponseWriter, req *http.Request) bool {
return true
}

View file

@ -1,8 +1,36 @@
// Package server is a child of package api to handle api calls concerning the server
package server
import "net/http"
import (
"encoding/json"
"net/http"
"github.com/Ennovar/gPanel/pkg/public"
)
func Restart(res http.ResponseWriter, req *http.Request, publicServer *public.Controller) bool {
if req.Method != "UPDATE" {
http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return false
}
var restartRequestData struct {
graceful bool `json:"graceful"`
}
err := json.NewDecoder(req.Body).Decode(&restartRequestData)
if err != nil {
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
err = publicServer.Restart(restartRequestData.graceful)
if err != nil {
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
res.WriteHeader(http.StatusNoContent)
func Restart(res http.ResponseWriter, req *http.Request) bool {
return true
}

View file

@ -1,2 +0,0 @@
// Package server is a child of package api to handle api calls concerning the server
package server

View file

@ -1,8 +1,36 @@
// Package server is a child of package api to handle api calls concerning the server
package server
import "net/http"
import (
"encoding/json"
"net/http"
"github.com/Ennovar/gPanel/pkg/public"
)
func Shutdown(res http.ResponseWriter, req *http.Request, publicServer *public.Controller) bool {
if req.Method != "UPDATE" {
http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return false
}
var shutdownRequestData struct {
graceful bool `json:"graceful"`
}
err := json.NewDecoder(req.Body).Decode(&shutdownRequestData)
if err != nil {
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
err = publicServer.Stop(shutdownRequestData.graceful)
if err != nil {
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
res.WriteHeader(http.StatusNoContent)
func Shutdown(res http.ResponseWriter, req *http.Request) bool {
return true
}

View file

@ -1,8 +1,24 @@
// Package server is a child of package api to handle api calls concerning the server
package server
import "net/http"
import (
"net/http"
func Start(res http.ResponseWriter, req *http.Request) bool {
"github.com/Ennovar/gPanel/pkg/public"
)
func Start(res http.ResponseWriter, req *http.Request, publicServer *public.Controller) bool {
if req.Method != "UPDATE" {
http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return false
}
err := publicServer.Start()
if err != nil {
http.Error(res, err.Error(), http.StatusConflict)
return false
}
res.WriteHeader(http.StatusNoContent)
return true
}

View file

@ -1,8 +1,21 @@
// Package server is a child of package api to handle api calls concerning the server
package server
import "net/http"
import (
"net/http"
"strconv"
"github.com/Ennovar/gPanel/pkg/public"
)
func Status(res http.ResponseWriter, req *http.Request, publicServer *public.Controller) bool {
if req.Method != "GET" {
http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return false
}
res.WriteHeader(http.StatusOK)
res.Write([]byte(strconv.Itoa(publicServer.Status)))
func Status(res http.ResponseWriter, req *http.Request) bool {
return true
}

View file

@ -11,6 +11,7 @@ func GetSecret(user string) (string, error) {
if err != nil {
return "", err
}
defer ds.Close()
err = ds.Get(database.BUCKET_USERS, []byte(user), &userDatabaseData)
if err != nil {

View file

@ -2,40 +2,139 @@
package public
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/Ennovar/gPanel/pkg/logging"
"github.com/Ennovar/gPanel/pkg/routing"
)
type PublicWeb struct {
Directory string
MaintenanceMode int
type Controller struct {
Directory string
GracefulShutdownTimeout time.Duration
Status int
}
// NewPublicWeb returns a new PublicWeb type.
func NewPublicWeb() PublicWeb {
return PublicWeb{
Directory: "document_roots/public/",
MaintenanceMode: 0,
var controller Controller
var server http.Server
// New function returns a new PublicWeb type.
func New() *Controller {
controller = Controller{
Directory: "document_roots/public/",
GracefulShutdownTimeout: 5 * time.Second,
Status: 0,
}
server = http.Server{
Addr: "localhost:3000",
Handler: &controller,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
MaxHeaderBytes: 0,
}
return &controller
}
// Start function starts listening on the public server
func (con *Controller) Start() error {
if con.Status == 1 {
return errors.New("Public server is already on.")
}
con.Status = 1
go server.ListenAndServe()
return nil
}
// Stop function stops the server gracefully or forceful, depending on the boolean input
func (con *Controller) Stop(graceful bool) error {
if graceful {
context, cancel := context.WithTimeout(context.Background(), con.GracefulShutdownTimeout)
defer cancel()
err := server.Shutdown(context)
if err != nil {
fmt.Printf("Graceful shutdown failed attempting forced: %v\n", err)
err = server.Close()
if err != nil {
return err
}
}
}
err := server.Close()
if err != nil {
return err
}
con.Status = 0
return nil
}
// Restart function combines both the start and stop function, using different
// status codes, as it is restarting.
func (con *Controller) Restart(graceful bool) error {
con.Status = 3
if graceful {
context, cancel := context.WithTimeout(context.Background(), con.GracefulShutdownTimeout)
defer cancel()
err := server.Shutdown(context)
if err != nil {
fmt.Printf("Graceful shutdown failed attempting forced: %v\n", err)
err = server.Close()
if err != nil {
return err
}
}
}
err := server.Close()
if err != nil {
return err
}
con.Status = 1
go server.ListenAndServe()
return nil
}
func (con *Controller) Maintenance() {
con.Status = 2
}
// ServeHTTP function routes all requests for the public web server. It is used in the main
// function inside of the http.ListenAndServe() function for the public host.
func (pub *PublicWeb) ServeHTTP(res http.ResponseWriter, req *http.Request) {
if pub.MaintenanceMode == 1 {
res.WriteHeader(http.StatusServiceUnavailable)
res.Write([]byte("We are currently in maintenance mode, please check back later."))
func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
switch con.Status {
case 0: // This will actually never show because this function won't run if the server is off
http.Error(res, "The server is currently down and not serving requests.", http.StatusServiceUnavailable)
return
case 1: // Normal
break
case 2: // Maintenance mode
http.Error(res, "The server is currently maintenance mode and not serving requests.", http.StatusServiceUnavailable)
return
case 3: // This will actually never show because this function won't run if the server is off
http.Error(res, "The server is currently restarting.", http.StatusServiceUnavailable)
return
}
path := req.URL.Path[1:]
if len(path) == 0 {
path = (pub.Directory + "index.html")
path = (con.Directory + "index.html")
} else {
path = (pub.Directory + path)
path = (con.Directory + path)
}
f, err := os.Open(path)

View file

@ -8,28 +8,31 @@ import (
"github.com/Ennovar/gPanel/pkg/api"
"github.com/Ennovar/gPanel/pkg/logging"
"github.com/Ennovar/gPanel/pkg/public"
"github.com/Ennovar/gPanel/pkg/routing"
)
type PrivateHost struct {
type Controller struct {
Directory string
Public *public.Controller
}
// NewPrivateHost returns a new PrivateHost type.
func NewPrivateHost() PrivateHost {
return PrivateHost{
// New returns a new PrivateHost type.
func New() Controller {
return Controller{
Directory: "document_roots/webhost/",
Public: public.New(),
}
}
// ServeHTTP function routes all requests for the private webhost server. It is used in the main
// function inside of the http.ListenAndServe() function for the private webhost host.
func (priv *PrivateHost) ServeHTTP(res http.ResponseWriter, req *http.Request) {
func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
path := req.URL.Path[1:]
if len(path) == 0 {
path = (priv.Directory + "index.html")
path = (con.Directory + "index.html")
} else {
path = (priv.Directory + path)
path = (con.Directory + path)
}
if reqAuth(path) {
@ -39,7 +42,7 @@ func (priv *PrivateHost) ServeHTTP(res http.ResponseWriter, req *http.Request) {
}
}
isApi, _ := api.HandleAPI(path, res, req)
isApi, _ := api.HandleAPI(res, req, path, con.Public)
if isApi {
// API methods handle HTTP logic from here