asterisk-outgoing-call-api/http.go
2021-05-14 06:30:19 -05:00

358 lines
8.6 KiB
Go

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)
}
}
}