func getIndexTpl(registry kit.Registry) ([]byte, apperror.Error) { if path := registry.Config().UString("frontend.indexTpl"); path != "" { f, err := os.Open(path) if err != nil { return nil, apperror.Wrap(err, "index_tpl_open_error", fmt.Sprintf("The index template at %v could not be opened", path)) } tpl, err := ioutil.ReadAll(f) if err != nil { return nil, apperror.Wrap(err, "index_tpl_read_error", fmt.Sprintf("Could not read index template at %v", path)) } return tpl, nil } tpl := ` <html> <body> <h1>Go Appkit</h1> <p>Welcome to your new appkit server.</p> <p> Find instructions on how to set up your app at <a href="http://github.com/app-kit/go-appkit">Github</a> </p> </body> </html> ` return []byte(tpl), nil }
func HandleFind(registry kit.Registry, request kit.Request) (kit.Response, bool) { collection := request.GetContext().MustString("collection") res := registry.Resource(collection) if res == nil || !res.IsPublic() { err := &apperror.Err{ Code: "unknown_resource", Message: fmt.Sprintf("The resource '%v' does not exist", collection), } return kit.NewErrorResponse(err), false } response, err := Find(res, request) if err != nil { response = kit.NewErrorResponse(err) } // If response contains a count and the request a "perPage" param, add a total_pages param // to meta. perPage, err2 := request.GetContext().Int("per_page") meta := response.GetMeta() if meta != nil && err2 == nil { count, ok := meta["count"] if ok { meta["total_pages"] = math.Ceil(float64(count.(int)) / float64(perPage)) } } return response, false }
func notFoundHandler(registry kit.Registry, r kit.Request) (kit.Response, bool) { httpRequest := r.GetHttpRequest() apiPrefix := "/" + registry.Config().UString("api.prefix", "api") isApiRequest := strings.HasPrefix(httpRequest.URL.Path, apiPrefix) // Try to render the page on the server, if enabled. if !isApiRequest { renderEnabled := registry.Config().UBool("serverRenderer.enabled", false) noRender := strings.Contains(httpRequest.URL.String(), "no-server-render") if renderEnabled && !noRender { return serverRenderer(registry, r), false } } // For non-api requests, render the default template. if !isApiRequest { tpl, err := getIndexTpl(registry) if err != nil { return kit.NewErrorResponse(err), false } return &kit.AppResponse{ RawData: tpl, }, false } // For api requests, render the api not found error. return &kit.AppResponse{ Error: &apperror.Err{ Code: "not_found", Message: "This api route does not exist", }, }, false }
func NewService(debug bool, registry kit.Registry) *Service { return &Service{ debug: debug, registry: registry, defaultBackend: registry.DefaultBackend(), resources: make(map[string]kit.Resource), } }
func HandleDelete(registry kit.Registry, request kit.Request) (kit.Response, bool) { collection := request.GetContext().MustString("collection") id := request.GetContext().MustString("id") res := registry.Resource(collection) if res == nil || !res.IsPublic() { resp := kit.NewErrorResponse("unknown_resource", fmt.Sprintf("The resource '%v' does not exist", collection)) return resp, false } return res.ApiDelete(id, request), false }
func AuthenticationMiddleware(registry kit.Registry, r kit.Request) (kit.Response, bool) { // Handle authentication. httpRequest := r.GetHttpRequest() userService := registry.UserService() if userService == nil { return nil, false } authHeader := httpRequest.Header.Get("Authentication") if authHeader == "" { return nil, false } // Check for basic auth. if strings.HasPrefix(authHeader, "Basic ") { str := authHeader[6:] data, err := base64.StdEncoding.DecodeString(str) if err != nil { return kit.NewErrorResponse("invalid_basic_auth"), false } else { parts := strings.Split(string(data), ":") if len(parts) == 2 { userIdentifier := parts[0] pw := parts[1] user, err := userService.AuthenticateUser(userIdentifier, "password", map[string]interface{}{"password": pw}) if err != nil { return kit.NewErrorResponse(err), false } r.SetUser(user) return nil, false } } } // Check for auth token. if authHeader != "" { token := authHeader user, session, err := userService.VerifySession(token) if err == nil { r.SetUser(user) r.SetSession(session) return nil, false } else { return kit.NewErrorResponse(err), false } } return nil, false }
func ServerErrorMiddleware(registry kit.Registry, r kit.Request, response kit.Response) (kit.Response, bool) { err := response.GetError() if err == nil { return nil, false } status := 500 // If the error is an apperror, and it contains a status, // set it as the http status of the response. if apperr, ok := err.(apperror.Error); ok { if apperr.GetStatus() != 0 { status = apperr.GetStatus() } } response.SetHttpStatus(status) if response.GetRawData() != nil || response.GetRawDataReader() != nil { return nil, false } httpRequest := r.GetHttpRequest() apiPrefix := "/" + registry.Config().UString("api.prefix", "api") isApiRequest := strings.HasPrefix(httpRequest.URL.Path, apiPrefix) if isApiRequest { return nil, false } data := map[string]interface{}{"errors": []error{response.GetError()}} tpl := defaultErrorTpl() tplPath := registry.Config().UString("frontend.errorTemplate") if tplPath != "" { t, err := template.ParseFiles(tplPath) if err != nil { registry.Logger().Fatalf("Could not parse error template at '%v': %v", tplPath, err) } else { tpl = t } } var buffer *bytes.Buffer if err := tpl.Execute(buffer, data); err != nil { registry.Logger().Fatalf("Could not render error template: %v\n", err) response.SetRawData([]byte("Server error")) } else { response.SetRawData(buffer.Bytes()) } return nil, false }
func processRequest(registry kit.Registry, request kit.Request, handler kit.RequestHandler) (kit.Response, bool) { var response kit.Response // Run before middlewares. for _, middleware := range registry.HttpFrontend().BeforeMiddlewares() { var skip bool response, skip = middleware(registry, request) if skip { return nil, true } else if response != nil { break } } // Only run the handler if no middleware provided a response. if response == nil { skip := false response, skip = handler(registry, request) if skip { return nil, true } } if response.GetHttpStatus() == 0 { // Note: error handler middleware will set proper http status for error responses. response.SetHttpStatus(200) } // Run after request middlewares. // Note: error responses are converted with the serverErrrorMiddleware middleware. // Note: serializing the response into the proper format is done with the SerializeResponseMiddleware. for _, middleware := range registry.HttpFrontend().AfterMiddlewares() { resp, skip := middleware(registry, request, response) if skip { return nil, true } else if resp != nil { response = resp } } return response, false }
func New(registry kit.Registry) *Frontend { conf := registry.Config() f := &Frontend{ registry: registry, debug: conf.UBool("frontends.wamp.debug", false), beforeMiddlewares: make([]kit.RequestHandler, 0), afterMiddlewares: make([]kit.AfterRequestMiddleware, 0), sessions: make(map[uint]kit.Session), } f.RegisterBeforeMiddleware(frontends.RequestTraceMiddleware) f.RegisterBeforeMiddleware(UnserializerMiddleware) f.RegisterAfterMiddleware(frontends.SerializeResponseMiddleware) f.RegisterAfterMiddleware(frontends.RequestTraceAfterMiddleware) f.RegisterAfterMiddleware(frontends.RequestLoggerMiddleware) return f }
func Update(registry kit.Registry, request kit.Request) (kit.Response, apperror.Error) { collection := request.GetContext().MustString("collection") res := registry.Resource(collection) if res == nil || !res.IsPublic() { return nil, &apperror.Err{ Code: "unknown_resource", Message: fmt.Sprintf("The resource '%v' does not exist", collection), } } model, ok := request.GetData().(kit.Model) if !ok { return nil, apperror.New("invalid_data_no_model", "Node model data in request.") } response := res.ApiUpdate(model, request) return response, nil }
func UnserializeRequestMiddleware(registry kit.Registry, request kit.Request) (kit.Response, bool) { // Try to parse json in body. Ignore error since body might not contain json. contentType := request.GetHttpRequest().Header.Get("Content-Type") if strings.Contains(contentType, "json") { // Only read the HTTP body automatically for json content type requests, // since some handlers might need to read it themselfes (see the files package resource). if err := request.ReadHttpBody(); err != nil { return kit.NewErrorResponse(err, "http_body_read_error"), false } else { if request.GetRawData() != nil { if err := request.ParseJsonData(); err != nil { return kit.NewErrorResponse(err, "invalid_json_body", true), false } if request.GetData() != nil { // Successfully parsed json body. // Now try to unserialize. // Determine serializer. serializer := registry.DefaultSerializer() // Check if a custom serializer was specified. if name := request.GetContext().String("request-serializer"); name != "" { serializer = registry.Serializer(name) } if serializer == nil { return kit.NewErrorResponse("unknown_serializer", fmt.Sprintf("The specified request serializer does not exist")), false } else { if err := request.Unserialize(serializer); err != nil { return kit.NewErrorResponse(err, "request_unserialize_error", true), false } } } } } } return nil, false }
func SerializeResponseMiddleware(registry kit.Registry, request kit.Request, response kit.Response) (kit.Response, bool) { // Try to serialize the reponse data. // Determine serializer. serializer := registry.DefaultSerializer() // Check if a custom serializer was specified. if name := request.GetContext().String("response-serializer"); name != "" { serializer = registry.Serializer(name) if serializer == nil { errResp := kit.NewErrorResponse("unknown_response_serializer", true) data, _ := serializer.MustSerializeResponse(errResp) errResp.SetData(data) return errResp, false } } // Set format in metadata. meta := response.GetMeta() if meta == nil { meta = make(map[string]interface{}) } meta["format"] = serializer.Name() response.SetMeta(meta) data, err := serializer.MustSerializeResponse(response) if err != nil { registry.Logger().Errorf("Response serialization error: %v (%+v)", err, response) } response.SetData(data) return nil, false }
func RequestLoggerMiddleware(registry kit.Registry, r kit.Request, response kit.Response) (kit.Response, bool) { // Calculate time taken. rawStarted, ok1 := r.GetContext().Get("startTime") rawFinished, ok2 := r.GetContext().Get("endTime") timeTaken := int64(-1) if ok1 && ok2 { started := rawStarted.(time.Time) finished := rawFinished.(time.Time) timeTaken = int64(finished.Sub(started) / time.Millisecond) } // Log the request. method := r.GetHttpMethod() path := r.GetPath() if response.GetError() != nil { registry.Logger().WithFields(logrus.Fields{ "frontend": r.GetFrontend(), "action": "request", "method": method, "path": path, "status": response.GetHttpStatus(), "err": response.GetError(), "milliseconds": timeTaken, }).Errorf("%v: %v - %v - %v", response.GetHttpStatus(), method, path, response.GetError()) } else { registry.Logger().WithFields(logrus.Fields{ "frontend": r.GetFrontend(), "action": "request", "method": method, "path": path, "status": response.GetHttpStatus(), "milliseconds": timeTaken, }).Debugf("%v: %v - %v", response.GetHttpStatus(), method, path) } return nil, false }
func UnserializerMiddleware(registry kit.Registry, request kit.Request) (kit.Response, bool) { serializer := registry.DefaultSerializer() // Try to find custom serializer. data, ok := request.GetData().(map[string]interface{}) if ok { name, ok := data["request_serializer"].(string) if ok { s := registry.Serializer(name) if s == nil { resp := kit.NewErrorResponse("unknown_request_serializer", fmt.Sprintf("The given request serializer %v does not exist", name)) return resp, false } else { serializer = s } } } if err := serializer.UnserializeRequest(request.GetData(), request); err != nil { return kit.NewErrorResponse(err), false } return nil, false }
func Create(registry kit.Registry, request kit.Request) (kit.Response, apperror.Error) { collection := request.GetContext().MustString("collection") res := registry.Resource(collection) if res == nil || !res.IsPublic() { return nil, &apperror.Err{ Code: "unknown_resource", Message: fmt.Sprintf("The resource '%v' does not exist", collection), } } fmt.Printf("data: %v | %+v\n\n", nil, request.GetData()) model, ok := request.GetData().(kit.Model) if !ok { return nil, apperror.New("invalid_data_no_model", "No model data in request.") } response := res.ApiCreate(model, request) if response.GetError() == nil { response.SetHttpStatus(201) } return response, nil }
func (r *FilesResource) getImageReader(registry kit.Registry, tmpDir string, file kit.File, width, height int64, filters []string, ip string) (reader kit.ReadSeekerCloser, size int64, err apperror.Error) { if width == 0 && height == 0 && len(filters) == 0 { reader, err = file.Reader() return } // Dimensions specified. // Check if the thumbnail was already created. // If so, serve it. Otherwise, create it first. if (width == 0 || height == 0) && (file.GetWidth() == 0 || file.GetHeight() == 0) { err = &apperror.Err{ Code: "image_dimensions_not_determined", Message: fmt.Sprintf("The file with id %v does not have width/height", file.GetId()), } return } if width < 0 || height < 0 { err = apperror.New("invalid_dimensions") return } // If either height or width is 0, determine proper values to presserve aspect ratio. if width == 0 { ratio := float64(file.GetWidth()) / float64(file.GetHeight()) width = int64(float64(height) * ratio) } else if height == 0 { ratio := float64(file.GetHeight()) / float64(file.GetWidth()) height = int64(float64(width) * ratio) } maxWidth := registry.Config().UInt("files.thumbGenerator.maxWidth", 2000) maxHeight := registry.Config().UInt("files.thumbGenerator.maxHeight", 2000) if width > int64(maxWidth) || height > int64(maxHeight) { err = &apperror.Err{ Code: "dimensions_exceed_maximum_limits", Message: "The specified dimensions exceed the maximum limits", } return } thumbId := fmt.Sprintf("%v_%v_%v_%v_%v_%v.%v", file.GetId(), file.GetBucket(), file.GetName(), strconv.FormatInt(width, 10), strconv.FormatInt(height, 10), strings.Replace(strings.Join(filters, "_"), ":", "_", -1), "jpeg") if ok, _ := file.GetBackend().HasFileById("thumbs", thumbId); !ok { var channel chan bool channel, err = r.thumbnailRateLimiter.Start(ip) if err != nil { return } if channel != nil { <-channel } // Thumb does not exist yet, so create it. reader, err = file.Reader() if err != nil { return } defer reader.Close() img, _, err2 := image.Decode(reader) if err2 != nil { err = apperror.Wrap(err2, "image_decode_error") return } var giftFilters []gift.Filter if !(height == 0 && width == 0) { giftFilters = append(giftFilters, gift.ResizeToFill(int(width), int(height), gift.LanczosResampling, gift.CenterAnchor)) } for _, filter := range filters { if filter == "" { continue } parts := strings.Split(filter, ":") if len(parts) > 1 { filter = parts[0] } switch filter { case "sepia": n := float32(100) if len(parts) == 2 { x, err2 := strconv.ParseFloat(parts[1], 64) if err2 == nil { n = float32(x) } else { err = apperror.New("invalid_sepia_filter_value", true) return } } giftFilters = append(giftFilters, gift.Sepia(n)) case "grayscale": giftFilters = append(giftFilters, gift.Grayscale()) case "brightness": n := float32(0) if len(parts) == 2 { x, err2 := strconv.ParseFloat(parts[1], 64) if err2 == nil { n = float32(x) } else { err = apperror.New("invalid_brightness_filter_value", true) return } } giftFilters = append(giftFilters, gift.Brightness(n)) default: err = apperror.New("unknown_filter", fmt.Sprintf("Unknown filter: %v", filter), true) return } } gift := gift.New(giftFilters...) thumb := image.NewRGBA(gift.Bounds(img.Bounds())) gift.Draw(thumb, img) var writer io.WriteCloser _, writer, err = file.GetBackend().WriterById("thumbs", thumbId, true) if err != nil { return } defer writer.Close() jpeg.Encode(writer, thumb, &jpeg.Options{Quality: 90}) r.thumbnailRateLimiter.Finish() } backend := file.GetBackend() size, err = backend.FileSizeById("thumbs", thumbId) if err != nil { return } reader, err = file.GetBackend().ReaderById("thumbs", thumbId) return }
func serverRenderer(registry kit.Registry, r kit.Request) kit.Response { url := r.GetHttpRequest().URL // Build the url to query. if url.Scheme == "" { url.Scheme = "http" } if url.Host == "" { url.Host = registry.Config().UString("host", "localhost") + ":" + registry.Config().UString("port", "8000") } q := url.Query() q.Set("no-server-render", "1") url.RawQuery = q.Encode() strUrl := url.String() cacheKey := "serverrenderer_" + strUrl cacheName := registry.Config().UString("serverRenderer.cache") var cache kit.Cache // If a cache is specified, try to retrieve it. if cacheName != "" { cache = registry.Cache(cacheName) if cache == nil { registry.Logger().Errorf("serverRenderer.cache is set to %v, but the cache is not registered with app", cacheName) } } // If a cache was found, try to retrieve cached response. if cache != nil { item, err := cache.Get(cacheKey) if err != nil { registry.Logger().Errorf("serverRenderer: cache retrieval error: %v", err) } else if item != nil { // Cache item found, return response with cache item. status, _ := strconv.ParseInt(item.GetTags()[0], 10, 64) data, _ := item.ToString() return &kit.AppResponse{ HttpStatus: int(status), RawData: []byte(data), } } } // Either no cache or url not yet cached, so render it. // First, ensure that the tmp directory exists. tmpDir := path.Join(registry.Config().TmpDir(), "phantom") if ok, _ := utils.FileExists(tmpDir); !ok { if err := os.MkdirAll(tmpDir, 0777); err != nil { return &kit.AppResponse{ Error: &apperror.Err{ Code: "create_tmp_dir_failed", Message: fmt.Sprintf("Could not create the tmp directory at %v: %v", tmpDir, err), }, } } } // Build a unique file name. filePath := path.Join(tmpDir, utils.UUIdv4()+".html") // Execute phantom js. // Find path of phantom script. _, filename, _, _ := runtime.Caller(1) scriptPath := path.Join(path.Dir(path.Dir(filename)), "phantom", "render.js") start := time.Now() phantomPath := registry.Config().UString("serverRenderer.phantomJsPath", "phantomjs") args := []string{ "--web-security=false", "--local-to-remote-url-access=true", scriptPath, "10", strUrl, filePath, } result, err := exec.Command(phantomPath, args...).CombinedOutput() if err != nil { registry.Logger().Errorf("Phantomjs execution error: %v", string(result)) return &kit.AppResponse{ Error: apperror.Wrap(err, "phantom_execution_failed"), } } // Get time taken as milliseconds. timeTaken := int(time.Now().Sub(start) / time.Millisecond) registry.Logger().WithFields(log.Fields{ "action": "phantomjs_render", "milliseconds": timeTaken, }).Debugf("Rendered url %v with phantomjs", url) content, err2 := utils.ReadFile(filePath) if err2 != nil { return kit.NewErrorResponse(err2) } // Find http status code. status := 200 res := regexp.MustCompile("http_status_code\\=(\\d+)").FindStringSubmatch(string(content)) if res != nil { s, _ := strconv.ParseInt(res[1], 10, 64) status = int(s) } // Save to cache. if cache != nil { lifetime := registry.Config().UInt("serverRenderer.cacheLiftetime", 3600) err := cache.Set(&caches.StrItem{ Key: cacheKey, Value: string(content), Tags: []string{strconv.FormatInt(int64(status), 10)}, ExpiresAt: time.Now().Add(time.Duration(lifetime) * time.Second), }) if err != nil { registry.Logger().Errorf("serverRenderer: Cache persist error: %v", err) } } return &kit.AppResponse{ HttpStatus: status, RawData: content, } }
func HttpHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params, registry kit.Registry, handler kit.RequestHandler) { config := registry.Config() header := w.Header() // Set Access-Control headers. allowedOrigins := config.UString("accessControl.allowedOrigins", "*") header.Set("Access-Control-Allow-Origin", allowedOrigins) methods := config.UString("accessControl.allowedMethods", "GET, POST, PUT, DELETE, OPTIONS, PATCH") header.Set("Access-Control-Allow-Methods", methods) allowedHeaders := config.UString("accessControl.allowedHeaders", "Authentication, Content-Type, X-Requested-With, Accept, Accept-Language, Content-Language") header.Set("Access-Control-Allow-Headers", allowedHeaders) // If it is an options request, just respond with 200. if r.Method == "OPTIONS" { w.WriteHeader(200) return } request := kit.NewRequest() request.SetFrontend("http") request.SetPath(r.URL.String()) request.SetHttpMethod(r.Method) request.SetHttpRequest(r) request.SetHttpResponseWriter(w) for _, param := range params { request.Context.Set(param.Key, param.Value) } queryVals := r.URL.Query() for key := range queryVals { vals := queryVals[key] if len(vals) == 1 { request.Context.Set(key, vals[0]) } else { request.Context.Set(key, vals) } } response, skip := processRequest(registry, request, handler) if skip { return } // If a data reader is set, write the data of the reader. reader := response.GetRawDataReader() if reader != nil { w.WriteHeader(response.GetHttpStatus()) io.Copy(w, reader) reader.Close() return } // If raw data is set, write the raw data. rawData := response.GetRawData() if rawData != nil { w.WriteHeader(response.GetHttpStatus()) w.Write(rawData) return } registry.Logger().Panicf("Invalid response with no raw data: %+v", response) }
func (s *Service) SetRegistry(x kit.Registry) { s.registry = x if s.backend == nil && x.DefaultBackend() != nil { s.SetBackend(x.DefaultBackend()) } }