James Coleman
3 years ago
commit
b7ff98764b
11 changed files with 591 additions and 0 deletions
-
1.gitignore
-
19License.txt
-
21README.md
-
87config.go
-
6example.call
-
4extension.conf
-
45flags.go
-
8go.mod
-
16go.sum
-
357http.go
-
27main.go
@ -0,0 +1 @@ |
|||
config.json |
@ -0,0 +1,19 @@ |
|||
Copyright (c) 2021 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/ |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
@ -0,0 +1,21 @@ |
|||
# Asterisk Outgoing Call API |
|||
|
|||
Starts an outgoing call based on provided paramters according to the https://wiki.asterisk.org/wiki/display/AST/Asterisk+Call+Files call file format. View https://www.voip-info.org/asterisk-auto-dial-out/ for more details about the call file format. I created this to make my phone ring when my cell phone rings via a tasker profile. Should be useful for many additional things though. |
|||
|
|||
# Accepted parameters |
|||
|
|||
- token: The API token to authenticate with the server. |
|||
- channel: Channel to use for the call. |
|||
- caller_id: Caller ID, Please note: It may not work if you do not respect the format: CallerID: “Some Name” <1234> |
|||
- wait_time: Seconds to wait for an answer. Default is 45. |
|||
- max_retries: Number of retries before failing (not including the initial attempt, e.g. 0 = total of 1 attempt to make the call). Default is 0. |
|||
- retry_time: Seconds between retries, Don’t hammer an unavailable phone. The default is 300 (5 min). |
|||
- account: Set the account code to use. |
|||
- application: Asterisk Application to run (use instead of specifying context, extension and priority). |
|||
- data: The options to be passed to application. |
|||
- context: Context in extensions.conf |
|||
- extension: Extension definition in extensions.conf |
|||
- priority: Priority of extension to start with. |
|||
- set_var: Set of variables to set in url query format. |
|||
- archive: Yes/No – Move to subdir “outgoing_done” with “Status: value”, where value can be Completed, Expired or Failed. |
|||
- schedule: Schedule call for a later date/time. Can be natrual language input as parsed by https://github.com/olebedev/when |
@ -0,0 +1,87 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"io/ioutil" |
|||
"log" |
|||
"os" |
|||
"os/user" |
|||
"path/filepath" |
|||
) |
|||
|
|||
// Reference from:
|
|||
// https://wiki.asterisk.org/wiki/display/AST/Asterisk+Call+Files
|
|||
|
|||
// Config all configurations for this application.
|
|||
type Config struct { |
|||
HTTPBind string `json:"http_bind"` |
|||
HTTPPort uint `json:"http_port"` |
|||
HTTPDebug bool `json:"http_debug"` |
|||
HTTPSystemDSocket bool `json:"http_systemd_socket"` |
|||
AsteriskSpoolDir string `json:"asterisk_spool_dir"` |
|||
DefaultChannel string `json:"default_channel"` |
|||
DefaultCallerId string `json:"default_caller_id"` |
|||
DefaultWaitTime uint64 `json:"default_wait_time"` // 5 seconds per ring.
|
|||
DefaultMaxRetries uint64 `json:"default_max_retries"` |
|||
DefaultRetryTime uint64 `json:"default_retry_time"` |
|||
DefaultAccount string `json:"default_account"` |
|||
DefaultApplication string `json:"default_application"` |
|||
DefaultData string `json:"default_data"` |
|||
PreventAPIApplication bool `json:"prevent_api_application"` // For security, prevent applications from being executed via API call.
|
|||
DefaultContext string `json:"default_context"` |
|||
DefaultExtension string `json:"default_extension"` |
|||
DefaultPriority string `json:"default_priority"` |
|||
DefaultSetVar map[string]string `json:"default_set_var"` |
|||
DefaultArchive bool `json:"default_archive"` |
|||
APIToken string `json:"api_token"` |
|||
} |
|||
|
|||
// ReadConfig read the configuration file into the config structure of the app.
|
|||
func (a *App) ReadConfig() { |
|||
// Get our current user for use in determining the home path.
|
|||
usr, err := user.Current() |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
|
|||
// Different configuration file paths.
|
|||
localConfig, _ := filepath.Abs("./config.json") |
|||
homeDirConfig := usr.HomeDir + "/.config/asterisk-outgoing-call-api/config.json" |
|||
etcConfig := "/etc/asterisk/outgoing-call-api.json" |
|||
|
|||
// Store defaults first.
|
|||
app.config = Config{ |
|||
HTTPPort: 9747, |
|||
HTTPDebug: false, |
|||
HTTPSystemDSocket: false, |
|||
AsteriskSpoolDir: "/var/spool/asterisk", |
|||
PreventAPIApplication: true, |
|||
DefaultArchive: false, |
|||
} |
|||
|
|||
// Determine which config file to use.
|
|||
var configFile string |
|||
if _, err := os.Stat(app.flags.ConfigPath); err == nil && app.flags.ConfigPath != "" { |
|||
configFile = app.flags.ConfigPath |
|||
} else if _, err := os.Stat(localConfig); err == nil { |
|||
configFile = localConfig |
|||
} else if _, err := os.Stat(homeDirConfig); err == nil { |
|||
configFile = homeDirConfig |
|||
} else if _, err := os.Stat(etcConfig); err == nil { |
|||
configFile = etcConfig |
|||
} else { |
|||
log.Fatal("Unable to find a configuration file.") |
|||
} |
|||
|
|||
// Read the config file.
|
|||
jsonFile, err := ioutil.ReadFile(configFile) |
|||
if err != nil { |
|||
log.Fatalf("Error reading JSON file: %v\n", err) |
|||
} |
|||
|
|||
// Parse the config file into the configuration structure.
|
|||
err = json.Unmarshal(jsonFile, &app.config) |
|||
if err != nil { |
|||
log.Fatalf("Error parsing JSON file: %v\n", err) |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
Channel: pjsip/103 |
|||
WaitTime: 15 |
|||
Context: Phone-Ring-Dummy-Answer |
|||
Extension: talk |
|||
Priority: 1 |
|||
Archive: no |
@ -0,0 +1,4 @@ |
|||
[Phone-Ring-Dummy-Answer] |
|||
exten => talk,1,Answer() |
|||
same => n,Playback(all-your-base) |
|||
same => n,Hangup() |
@ -0,0 +1,45 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"flag" |
|||
"fmt" |
|||
"os" |
|||
) |
|||
|
|||
// Flags are command line tick options.
|
|||
type Flags struct { |
|||
ConfigPath string |
|||
HTTPBind string |
|||
HTTPPort uint |
|||
} |
|||
|
|||
// Init configures the golang flags and parses the command line provided options.
|
|||
func (f *Flags) Init() { |
|||
flag.Usage = func() { |
|||
fmt.Printf("asterisk-outgoing-call-api: Make an outgoing call via an API call.\n\nUsage:\n") |
|||
flag.PrintDefaults() |
|||
} |
|||
|
|||
var printVersion bool |
|||
flag.BoolVar(&printVersion, "v", false, "Print version") |
|||
|
|||
var usage string |
|||
usage = "Load configuration from file." |
|||
flag.StringVar(&f.ConfigPath, "config", "", usage) |
|||
flag.StringVar(&f.ConfigPath, "c", "", usage+" (shorthand)") |
|||
|
|||
usage = "Bind address for http server" |
|||
flag.StringVar(&f.HTTPBind, "http-bind", "", usage) |
|||
flag.StringVar(&f.HTTPBind, "b", "", usage+" (shorthand)") |
|||
|
|||
usage = "Bind port for http server" |
|||
flag.UintVar(&f.HTTPPort, "http-port", 0, usage) |
|||
flag.UintVar(&f.HTTPPort, "p", 0, usage+" (shorthand)") |
|||
|
|||
flag.Parse() |
|||
|
|||
if printVersion { |
|||
fmt.Println("asterisk-outgoing-call-api: 0.1") |
|||
os.Exit(0) |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
module github.com/GRMrGecko/asterisk-outgoing-call-api |
|||
|
|||
go 1.16 |
|||
|
|||
require ( |
|||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf |
|||
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254 |
|||
) |
@ -0,0 +1,16 @@ |
|||
github.com/AlekSi/pointer v1.0.0 h1:KWCWzsvFxNLcmM5XmiqHsGTTsuwZMsLFwWF9Y+//bNE= |
|||
github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8= |
|||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= |
|||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= |
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|||
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254 h1:JYoQR67E1vv1WGoeW8DkdFs7vrIEe/5wP+qJItd5tUE= |
|||
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0= |
|||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= |
|||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
|||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= |
|||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
@ -0,0 +1,357 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/json" |
|||
"fmt" |
|||
"log" |
|||
"math/rand" |
|||
"net/http" |
|||
"net/url" |
|||
"os" |
|||
"os/signal" |
|||
"path" |
|||
"strconv" |
|||
"strings" |
|||
"syscall" |
|||
"time" |
|||
|
|||
"github.com/coreos/go-systemd/activation" |
|||
"github.com/olebedev/when" |
|||
"github.com/olebedev/when/rules/common" |
|||
"github.com/olebedev/when/rules/en" |
|||
) |
|||
|
|||
// HTTPServer the http server structure.
|
|||
type HTTPServer struct { |
|||
} |
|||
|
|||
// Common strings.
|
|||
const ( |
|||
APIOK = "ok" |
|||
APIERR = "error" |
|||
) |
|||
|
|||
// APIGeneralResp General response to API requests.
|
|||
type APIGeneralResp struct { |
|||
Status string `json:"status"` |
|||
Error string `json:"error"` |
|||
} |
|||
|
|||
// JSONResponse Takes a golang structure and converts it to a JSON object for response.
|
|||
func (s *HTTPServer) JSONResponse(w http.ResponseWriter, resp interface{}) { |
|||
// Encode response as json.
|
|||
js, err := json.Marshal(resp) |
|||
if err != nil { |
|||
// Error should not happen normally...
|
|||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) |
|||
return |
|||
} |
|||
|
|||
// If no err, we can set content type header and send response.
|
|||
w.Header().Set("Content-Type", "application/json") |
|||
w.Write(js) |
|||
w.Write([]byte{'\n'}) |
|||
} |
|||
|
|||
// APISendGeneralResp Send a standard response.
|
|||
func (s *HTTPServer) APISendGeneralResp(w http.ResponseWriter, status, err string) { |
|||
resp := APIGeneralResp{} |
|||
resp.Status = status |
|||
resp.Error = err |
|||
s.JSONResponse(w, resp) |
|||
} |
|||
|
|||
// registerHandlers HTTP server handlers.
|
|||
func (s *HTTPServer) registerHandlers(r *http.ServeMux) { |
|||
// For this project, we only handle requests to /.
|
|||
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
// Parse form data.
|
|||
err := r.ParseMultipartForm(32 << 20) |
|||
if err == http.ErrNotMultipart { |
|||
err = r.ParseForm() |
|||
} |
|||
if err != nil { |
|||
fmt.Println(err) |
|||
s.APISendGeneralResp(w, APIERR, "Bad request") |
|||
return |
|||
} |
|||
|
|||
// Verify we are authorized.
|
|||
if r.Form.Get("token") != app.config.APIToken { |
|||
s.APISendGeneralResp(w, APIERR, "Unauthorized") |
|||
return |
|||
} |
|||
|
|||
// Get call details.
|
|||
channel := r.Form.Get("channel") |
|||
if channel == "" { |
|||
channel = app.config.DefaultChannel |
|||
} |
|||
|
|||
callerId := r.Form.Get("caller_id") |
|||
if callerId == "" { |
|||
callerId = app.config.DefaultCallerId |
|||
} |
|||
|
|||
waitTime, err := strconv.ParseUint(r.Form.Get("wait_time"), 10, 64) |
|||
if err != nil { |
|||
waitTime = app.config.DefaultWaitTime |
|||
} |
|||
|
|||
maxRetries, err := strconv.ParseUint(r.Form.Get("max_retries"), 10, 64) |
|||
if err != nil { |
|||
maxRetries = app.config.DefaultMaxRetries |
|||
} |
|||
|
|||
retryTime, err := strconv.ParseUint(r.Form.Get("retry_time"), 10, 64) |
|||
if err != nil { |
|||
retryTime = app.config.DefaultRetryTime |
|||
} |
|||
|
|||
account := r.Form.Get("account") |
|||
if account == "" { |
|||
account = app.config.DefaultCallerId |
|||
} |
|||
|
|||
application := r.Form.Get("application") |
|||
if application == "" || app.config.PreventAPIApplication { |
|||
application = app.config.DefaultApplication |
|||
} |
|||
|
|||
data := r.Form.Get("data") |
|||
if data == "" || app.config.PreventAPIApplication { |
|||
data = app.config.DefaultData |
|||
} |
|||
|
|||
context := r.Form.Get("context") |
|||
if context == "" { |
|||
context = app.config.DefaultContext |
|||
} |
|||
|
|||
extension := r.Form.Get("extension") |
|||
if context == "" { |
|||
extension = app.config.DefaultExtension |
|||
} |
|||
|
|||
priority := r.Form.Get("priority") |
|||
if context == "" { |
|||
priority = app.config.DefaultPriority |
|||
} |
|||
|
|||
setVar := make(map[string]string) |
|||
parsed, err := url.ParseQuery(r.Form.Get("set_var")) |
|||
if err != nil { |
|||
setVar = app.config.DefaultSetVar |
|||
} else { |
|||
for key, value := range parsed { |
|||
setVar[key] = value[0] |
|||
} |
|||
} |
|||
|
|||
archiveVal := strings.ToLower(r.Form.Get("archive")) |
|||
archive := false |
|||
if archiveVal == "true" || archiveVal == "yes" { |
|||
archive = true |
|||
} else if archiveVal != "false" && archiveVal != "no" { |
|||
archive = app.config.DefaultArchive |
|||
} |
|||
|
|||
schedule := r.Form.Get("schedule") |
|||
|
|||
if channel == "" || (application == "" && context == "") { |
|||
s.APISendGeneralResp(w, APIERR, "Required options not set") |
|||
return |
|||
} |
|||
|
|||
// Setup call file details.
|
|||
outgoingCallName := "outgoing-call-" + strconv.Itoa(rand.Int()) |
|||
spoolFileName := path.Join(app.config.AsteriskSpoolDir, outgoingCallName) |
|||
outgoingFileName := path.Join(app.config.AsteriskSpoolDir, "outgoing", outgoingCallName) |
|||
|
|||
callFile, err := os.Create(spoolFileName) |
|||
if err != nil { |
|||
fmt.Println(err) |
|||
s.APISendGeneralResp(w, APIERR, "Unable to create call file") |
|||
return |
|||
} |
|||
|
|||
// Write call details.
|
|||
callFile.WriteString("Channel: " + channel + "\n") |
|||
|
|||
if callerId != "" { |
|||
callFile.WriteString("Callerid: " + callerId + "\n") |
|||
} |
|||
|
|||
if waitTime != 0 { |
|||
callFile.WriteString("WaitTime: " + strconv.FormatUint(waitTime, 10) + "\n") |
|||
} |
|||
|
|||
if maxRetries != 0 { |
|||
callFile.WriteString("MaxRetries: " + strconv.FormatUint(maxRetries, 10) + "\n") |
|||
} |
|||
|
|||
if retryTime != 0 { |
|||
callFile.WriteString("RetryTime: " + strconv.FormatUint(retryTime, 10) + "\n") |
|||
} |
|||
|
|||
if account != "" { |
|||
callFile.WriteString("Account: " + account + "\n") |
|||
} |
|||
|
|||
if application != "" { |
|||
callFile.WriteString("Application: " + application + "\n") |
|||
} |
|||
|
|||
if data != "" { |
|||
callFile.WriteString("Data: " + data + "\n") |
|||
} |
|||
|
|||
if context != "" { |
|||
callFile.WriteString("Context: " + context + "\n") |
|||
} |
|||
|
|||
if extension != "" { |
|||
callFile.WriteString("Extension: " + extension + "\n") |
|||
} |
|||
|
|||
if priority != "" { |
|||
callFile.WriteString("Priority: " + priority + "\n") |
|||
} |
|||
|
|||
for key, value := range setVar { |
|||
callFile.WriteString("Setvar: " + key + "=" + value + "\n") |
|||
} |
|||
|
|||
if archive { |
|||
callFile.WriteString("Archive: yes\n") |
|||
} else { |
|||
callFile.WriteString("Archive: no\n") |
|||
} |
|||
|
|||
callFile.Close() |
|||
|
|||
if schedule != "" { |
|||
now := time.Now() |
|||
w := when.New(nil) |
|||
w.Add(en.All...) |
|||
w.Add(common.All...) |
|||
parsedTime, _ := w.Parse(schedule, now) |
|||
if parsedTime == nil { |
|||
parsedTime = new(when.Result) |
|||
parsedTime.Time = now |
|||
} |
|||
|
|||
os.Chtimes(spoolFileName, parsedTime.Time, parsedTime.Time) |
|||
} |
|||
|
|||
// Add call to the outgoing call queue.
|
|||
err = os.Rename(spoolFileName, outgoingFileName) |
|||
if err != nil { |
|||
fmt.Println(err) |
|||
s.APISendGeneralResp(w, APIERR, "Unable to move call file into outgoing directory") |
|||
return |
|||
} |
|||
|
|||
// Send final response.
|
|||
s.APISendGeneralResp(w, APIOK, "") |
|||
}) |
|||
} |
|||
|
|||
func HTTPServe() { |
|||
// Used to reset the app quit timeout for systemd sockets.
|
|||
var timeoutReset chan struct{} |
|||
|
|||
// Create the server.
|
|||
httpServer := new(HTTPServer) |
|||
app.httpServer = httpServer |
|||
|
|||
// Setup the handlers.
|
|||
r := http.NewServeMux() |
|||
httpServer.registerHandlers(r) |
|||
|
|||
// The http server handler will be the mux router by default.
|
|||
var handler http.Handler |
|||
handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
|||
if app.config.HTTPSystemDSocket { |
|||
timeoutReset <- struct{}{} |
|||
} |
|||
if app.config.HTTPDebug { |
|||
log.Println(req.Method + " " + req.URL.String()) |
|||
} |
|||
r.ServeHTTP(w, req) |
|||
}) |
|||
|
|||
// Determine if we're using a systemd socket activation or just a standard listen.
|
|||
if app.config.HTTPSystemDSocket { |
|||
done := make(chan struct{}) |
|||
quit := make(chan os.Signal, 1) |
|||
timeoutReset = make(chan struct{}) |
|||
|
|||
// On signal, gracefully shut down the server and wait 5
|
|||
// seconds for current connection to stop.
|
|||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) |
|||
|
|||
// Pull existing listener from systemd.
|
|||
listeners, err := activation.Listeners() |
|||
if err != nil { |
|||
log.Panicf("Cannot retrieve listeners: %v", err) |
|||
} |
|||
|
|||
// If we already have a asterisk-outgoing-call-api running, then we shouldn't start...
|
|||
if len(listeners) != 1 { |
|||
log.Panicf("Unexpected number of socket activation (%d != 1)", len(listeners)) |
|||
} |
|||
|
|||
server := &http.Server{ |
|||
Handler: handler, |
|||
} |
|||
|
|||
// Upon signal, close out existing connection and quit.
|
|||
go func() { |
|||
<-quit |
|||
log.Println("Server is shutting down") |
|||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
|||
defer cancel() |
|||
server.SetKeepAlivesEnabled(false) |
|||
if err := server.Shutdown(ctx); err != nil { |
|||
log.Panicf("Cannot gracefully shut down the server: %v", err) |
|||
} |
|||
close(done) |
|||
}() |
|||
|
|||
// 30 minute time out if no connection is received.
|
|||
go func() { |
|||
for { |
|||
select { |
|||
case <-timeoutReset: |
|||
case <-time.After(30 * time.Minute): |
|||
close(quit) |
|||
} |
|||
} |
|||
}() |
|||
|
|||
// Listen on existing systemd socket.
|
|||
server.Serve(listeners[0]) |
|||
|
|||
// Wait for existing connections befor exiting.
|
|||
<-done |
|||
} else { |
|||
// Get the configuration.
|
|||
httpBind := app.config.HTTPBind |
|||
httpPort := app.config.HTTPPort |
|||
if app.flags.HTTPBind != "" { |
|||
httpBind = app.flags.HTTPBind |
|||
} |
|||
if app.flags.HTTPPort != 0 { |
|||
httpPort = app.flags.HTTPPort |
|||
} |
|||
|
|||
// Start the server.
|
|||
log.Println("Starting the http server on port", httpPort) |
|||
err := http.ListenAndServe(fmt.Sprintf("%s:%d", httpBind, httpPort), handler) |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"math/rand" |
|||
"time" |
|||
) |
|||
|
|||
// App is the standard structure that allows different parts of the application to access common parameters/configuration.
|
|||
type App struct { |
|||
flags *Flags |
|||
httpServer *HTTPServer |
|||
config Config |
|||
} |
|||
|
|||
var app *App |
|||
|
|||
func main() { |
|||
// We use rand for file naming, best set seed at start.
|
|||
rand.Seed(time.Now().UnixNano()) |
|||
|
|||
app = new(App) |
|||
app.flags = new(Flags) |
|||
app.flags.Init() |
|||
app.ReadConfig() |
|||
|
|||
HTTPServe() |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue