github.com/pavlo67/common@v0.5.3/common/server_http/server_http_jschmhr/jschmhr.go (about)

     1  package server_http_jschmhr
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/julienschmidt/httprouter"
    15  
    16  	"github.com/pavlo67/common/common/auth"
    17  	"github.com/pavlo67/common/common/errors"
    18  	"github.com/pavlo67/common/common/server_http"
    19  )
    20  
    21  var _ server_http.Operator = &serverHTTPJschmhr{}
    22  
    23  type serverHTTPJschmhr struct {
    24  	httpServer   *http.Server
    25  	httpServeMux *httprouter.Router
    26  
    27  	port        int
    28  	tlsCertFile string
    29  	tlsKeyFile  string
    30  
    31  	onRequest server_http.OnRequestMiddleware
    32  
    33  	secretENVsToLower []string
    34  }
    35  
    36  func New(port int, tlsCertFile, tlsKeyFile string, secretENVs []string) (server_http.Operator, error) {
    37  	if port <= 0 {
    38  		return nil, fmt.Errorf("on server_http_jschmhr.New(): wrong port = %d", port)
    39  	}
    40  
    41  	var secretENVsToLower []string
    42  	for _, secretENV := range secretENVs {
    43  		secretENVsToLower = append(secretENVsToLower, strings.ToLower(secretENV))
    44  	}
    45  
    46  	router := httprouter.New()
    47  
    48  	return &serverHTTPJschmhr{
    49  		httpServer: &http.Server{
    50  			Handler:        router,
    51  			ReadTimeout:    60 * time.Second,
    52  			WriteTimeout:   60 * time.Second,
    53  			MaxHeaderBytes: 1 << 20,
    54  
    55  			// ReadHeaderTimeout:            60 * time.Second,
    56  			// IdleTimeout:                  60 * time.Second,
    57  			// DisableGeneralOptionsHandler: false,
    58  		},
    59  		httpServeMux: router,
    60  		port:         port,
    61  		tlsCertFile:  tlsCertFile,
    62  		tlsKeyFile:   tlsKeyFile,
    63  
    64  		secretENVsToLower: secretENVsToLower,
    65  	}, nil
    66  }
    67  
    68  // start wraps and verbalizes http.Server.ListenAndServe method.
    69  func (s *serverHTTPJschmhr) Start() error {
    70  	if s == nil {
    71  		return errors.New("no serverOp to start")
    72  	}
    73  
    74  	s.httpServer.Addr = ":" + strconv.Itoa(s.port)
    75  	l.Info("Server is starting on address ", s.httpServer.Addr)
    76  
    77  	if s.tlsCertFile != "" && s.tlsKeyFile != "" {
    78  		return s.httpServer.ListenAndServeTLS(s.tlsCertFile, s.tlsKeyFile)
    79  	}
    80  
    81  	return s.httpServer.ListenAndServe()
    82  }
    83  
    84  func (s *serverHTTPJschmhr) Shutdown(ctx context.Context) error {
    85  	if s == nil {
    86  		return errors.New("no serverOp to shutting down")
    87  	}
    88  
    89  	return s.Shutdown(ctx)
    90  }
    91  
    92  func (s *serverHTTPJschmhr) Addr() (port int, https bool) {
    93  	return s.port, s.tlsCertFile != "" && s.tlsKeyFile != ""
    94  }
    95  
    96  //func (s *serverHTTPJschmhr) ServerHTTP() *http.Server {
    97  //	return s.httpServer
    98  //}
    99  
   100  const onHandleMiddleware = "on serverHTTPJschmhr.HandleMiddleware()"
   101  
   102  func (s *serverHTTPJschmhr) HandleMiddleware(onRequest server_http.OnRequestMiddleware) error {
   103  	if s.onRequest != nil && onRequest != nil {
   104  		return fmt.Errorf(onHandleMiddleware + ": can't add middlware twice")
   105  	}
   106  
   107  	s.onRequest = onRequest
   108  	return nil
   109  }
   110  
   111  const onHandleEndpoint = "on serverHTTPJschmhr.HandleEndpoint()"
   112  
   113  func (s *serverHTTPJschmhr) HandleEndpoint(key server_http.EndpointKey, serverPath string, endpoint server_http.Endpoint) error {
   114  
   115  	method := strings.ToUpper(endpoint.Method)
   116  	path := endpoint.PathTemplate(serverPath)
   117  
   118  	if endpoint.WorkerHTTP == nil {
   119  		return errors.New(onHandleEndpoint + ": " + method + ": " + path + "\t!!! NULL workerHTTP ISN'T DISPATCHED !!!")
   120  	}
   121  
   122  	s.HandleOptions(key, path)
   123  
   124  	handler := func(w http.ResponseWriter, r *http.Request, paramsHR httprouter.Params) {
   125  
   126  		w.Header().Set("Access-Control-Allow-Origin", server_http.CORSAllowOrigin)
   127  		w.Header().Set("Access-Control-Allow-Headers", server_http.CORSAllowHeaders)
   128  		w.Header().Set("Access-Control-Allow-Methods", server_http.CORSAllowMethods)
   129  		w.Header().Set("Access-Control-Allow-Credentials", server_http.CORSAllowCredentials)
   130  
   131  		var identity *auth.Identity
   132  		if s.onRequest != nil {
   133  			var err error
   134  			if identity, err = s.onRequest.Identity(r); err != nil {
   135  				l.Error(err)
   136  			}
   137  		}
   138  
   139  		var params server_http.PathParams
   140  		if len(paramsHR) > 0 {
   141  			params = server_http.PathParams{}
   142  			for _, p := range paramsHR {
   143  				params[p.Key] = p.Value
   144  			}
   145  		}
   146  
   147  		responseData, err := endpoint.WorkerHTTP(s, r, params, identity)
   148  		if err != nil {
   149  			l.Warn(err)
   150  		}
   151  
   152  		if responseData.MIMEType != "" {
   153  			w.Header().Set("Content-Type", responseData.MIMEType)
   154  		}
   155  		w.Header().Set("Content-Length", strconv.Itoa(len(responseData.Data)))
   156  		if responseData.FileName != "" {
   157  			w.Header().Set("Content-Disposition", "attachment; filename="+responseData.FileName)
   158  		}
   159  
   160  		if responseData.Status > 0 {
   161  			w.WriteHeader(responseData.Status)
   162  		} else {
   163  			w.WriteHeader(http.StatusOK)
   164  		}
   165  
   166  		if _, err = w.Write(responseData.Data); err != nil {
   167  			l.Errorf("can't write response (%s): %s", serverPath, err)
   168  		}
   169  	}
   170  
   171  	l.Infof("%-10s: %s %s", key, method, path)
   172  	switch method {
   173  	case "GET":
   174  		s.httpServeMux.GET(path, handler)
   175  	case "POST":
   176  		s.httpServeMux.POST(path, handler)
   177  	case "PUT":
   178  		s.httpServeMux.PUT(path, handler)
   179  	case "DELETE":
   180  		s.httpServeMux.DELETE(path, handler)
   181  	default:
   182  		return fmt.Errorf(onHandleEndpoint+": method (%s) isn't supported", method)
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  func (s *serverHTTPJschmhr) HandleOptions(key server_http.EndpointKey, serverPath string) {
   189  	//if strlib.In(s.handledOptions, serverPath) {
   190  	//	//l.Infof("- %#v", s.handledOptions)
   191  	//	return
   192  	//}
   193  
   194  	s.httpServeMux.OPTIONS(serverPath, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
   195  		l.Infof("%-10s: OPTIONS %s", key, serverPath)
   196  		w.Header().Set("Access-Control-Allow-Origin", server_http.CORSAllowOrigin)
   197  		w.Header().Set("Access-Control-Allow-Headers", server_http.CORSAllowHeaders)
   198  		w.Header().Set("Access-Control-Allow-Methods", server_http.CORSAllowMethods)
   199  		w.Header().Set("Access-Control-Allow-Credentials", server_http.CORSAllowCredentials)
   200  	})
   201  
   202  	//s.handledOptions = append(s.handledOptions, serverPath)
   203  }
   204  
   205  var reHTMLExt = regexp.MustCompile(`\.html?$`)
   206  
   207  func (s *serverHTTPJschmhr) HandleFiles(key server_http.EndpointKey, serverPath string, staticPath server_http.StaticPath) error {
   208  	l.Infof("%-10s: FILES %s <-- %s", key, serverPath, staticPath.LocalPath)
   209  
   210  	// TODO: check localPath
   211  
   212  	if staticPath.MIMEType == nil {
   213  		// TODO!!! CORS
   214  
   215  		s.httpServeMux.ServeFiles(serverPath, http.Dir(staticPath.LocalPath))
   216  		return nil
   217  	}
   218  
   219  	s.HandleOptions(key, serverPath)
   220  
   221  	//fileServer := http.FileServer(http.Dir(localPath))
   222  	s.httpServeMux.GET(serverPath, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
   223  		w.Header().Set("Access-Control-Allow-Origin", server_http.CORSAllowOrigin)
   224  		w.Header().Set("Access-Control-Allow-Headers", server_http.CORSAllowHeaders)
   225  		w.Header().Set("Access-Control-Allow-Methods", server_http.CORSAllowMethods)
   226  		w.Header().Set("Access-Control-Allow-Credentials", server_http.CORSAllowCredentials)
   227  
   228  		if staticPath.MIMEType != nil && *staticPath.MIMEType != "" {
   229  			w.Header().Set("Content-Type", *staticPath.MIMEType)
   230  		}
   231  
   232  		OpenFile, err := os.Open(staticPath.LocalPath + "/" + p.ByName("filepath"))
   233  		defer OpenFile.Close()
   234  		if err != nil {
   235  			l.Error(err)
   236  		} else {
   237  			io.Copy(w, OpenFile)
   238  		}
   239  
   240  		//if mimeType != nil {
   241  		//}
   242  		//fileServer.ServeHTTP(w, r)
   243  	})
   244  
   245  	return nil
   246  }
   247  
   248  // mimeTypeToSet, err = inspector.MIME(localPath+"/"+r.ExportID.PathWithParams, nil)
   249  // if err != nil {
   250  //	l.ErrStr("can't read MIMEType for file: ", localPath+"/"+r.ExportID.PathWithParams, err)
   251  // }
   252  
   253  //func (s *serverHTTPJschmhr) HandleGetString(serverRoute, str string, mimeType *string) {
   254  //	s.handleFunc("GET", serverRoute, func(w http.ResponseWriter, r *http.Request, params httprouter.Content) {
   255  //		if mimeType != nil {
   256  //			// "application/javascript"
   257  //			w.Header().Set("Content-Type", *mimeType)
   258  //		}
   259  //		w.Write([]byte(str))
   260  //	})
   261  //}