Example #1
0
File: api.go Project: z0rr0/luss
// HandlerGet returns info about short URLs.
func HandlerGet(ctx context.Context, w http.ResponseWriter, r *http.Request) core.ErrHandler {
	var grs []getRequest
	c, err := conf.FromContext(ctx)
	if err != nil {
		return core.ErrHandler{Err: err, Status: http.StatusInternalServerError}
	}
	defer r.Body.Close()
	decoder := json.NewDecoder(r.Body)
	err = decoder.Decode(&grs)
	if (err != nil) && (err != io.EOF) {
		return core.ErrHandler{Err: err, Status: http.StatusBadRequest}
	}
	links := []string{}
	for i := range grs {
		link, err := core.TrimAddress(grs[i].Short)
		if err != nil {
			c.L.Debug.Printf("invalid short URL [%v] was skipped: %v", link, err)
			continue
		}
		if l, ok := trim.IsShort(link); ok {
			links = append(links, l)
		}
	}
	if len(links) == 0 {
		return core.ErrHandler{Err: ErrEmptyRequest, Status: http.StatusNoContent}
	}
	cus, err := trim.MultiLengthen(ctx, links)
	if err != nil {
		return core.ErrHandler{Err: err, Status: http.StatusInternalServerError}
	}
	items := make([]addResponseItem, len(cus))
	for i, cu := range cus {
		id := cu.Cu.String()
		items[i] = addResponseItem{
			ID:       id,
			Short:    c.Address(id),
			Original: cu.Cu.Original,
			Err:      cu.Err,
		}
	}
	result := &addResponse{
		Err:    0,
		Msg:    "ok",
		Result: items,
	}
	b, err := json.Marshal(result)
	if err != nil {
		return core.ErrHandler{Err: err, Status: http.StatusInternalServerError}
	}
	w.Header().Set("Content-Type", "application/json")
	fmt.Fprintf(w, "%s", b)
	return core.ErrHandler{Err: nil, Status: http.StatusOK}
}
Example #2
0
File: luss.go Project: z0rr0/luss
func main() {
	var err error
	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("abnormal termination [%v]: %v\n", Version, r)
		}
	}()
	version := flag.Bool("version", false, "show version")
	config := flag.String("config", Config, "configuration file")
	flag.Parse()
	if *version {
		fmt.Printf("%v: %v\n\trevision: %v %v\n\tbuild date: %v\n", Name, Version, Revision, runtime.Version(), BuildDate)
		return
	}
	// configuration initialization
	cfg, err := conf.Parse(*config)
	if err != nil {
		log.Panicf("init config error [%v]", err)
	}
	if err := cfg.Validate(); err != nil {
		log.Panicf("config validate error [%v]", err)
	}
	// check db connection
	s, err := db.NewSession(cfg.Conn, true)
	if err != nil {
		log.Panic(err)
	}
	s.Close()
	defer cfg.Close()
	// init users
	if err := auth.InitUsers(cfg); err != nil {
		log.Panic(err)
	}
	// set init context
	mainCtx := conf.NewContext(cfg)
	mainCtx, err = core.RunWorkers(mainCtx)
	if err != nil {
		log.Panic(err)
	}
	go core.CleanWorker(cfg)
	errc := make(chan error)
	go func() {
		errc <- interrupt()
	}()
	listener := net.JoinHostPort(cfg.Listener.Host, fmt.Sprint(cfg.Listener.Port))
	cfg.L.Info.Printf("%v running (debug=%v):\n\tlisten: %v\n\tgo version: %v\n\tversion=%v [%v %v]",
		Name,
		cfg.Debug,
		listener,
		GoVersion,
		Version,
		Revision,
		BuildDate,
	)
	server := &http.Server{
		Addr:           listener,
		Handler:        http.DefaultServeMux,
		ReadTimeout:    time.Duration(cfg.Listener.Timeout) * time.Second,
		WriteTimeout:   time.Duration(cfg.Listener.Timeout) * time.Second,
		MaxHeaderBytes: 1 << 20,
		ErrorLog:       cfg.L.Error,
	}
	maxSize := cfg.Settings.MaxReqSize << 20
	// static files
	staticDir, _ := cfg.StaticDir()
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
	// keys should not match to trim.IsShortURL pattern (short URLs set)
	handlers := map[string]Handler{
		"/":              {F: core.HandlerIndex, Auth: false, API: false, Method: "ANY"},
		"/test/t":        {F: core.HandlerTest, Auth: false, API: false, Method: "ANY"},
		"/error/notfoud": {F: core.HandlerNotFound, Auth: false, API: false, Method: "GET"},
		"/error/common":  {F: core.HandlerError, Auth: false, API: false, Method: "GET"},
		"/api/noweb":     {F: core.HandlerNoWebIndex, Auth: false, API: false, Method: "ANY"},
		"/api/info":      {F: api.HandlerInfo, Auth: false, API: true, Method: "GET"},
		"/api/add":       {F: api.HandlerAdd, Auth: false, API: true, Method: "POST"},
		"/api/get":       {F: api.HandlerGet, Auth: false, API: true, Method: "POST"},
		"/api/user/add":  {F: api.HandlerUserAdd, Auth: true, API: true, Method: "POST"},
		"/api/user/pwd":  {F: api.HandlerPwd, Auth: true, API: true, Method: "POST"},
		"/api/user/del":  {F: api.HandlerUserDel, Auth: true, API: true, Method: "POST"},
		"/api/import":    {F: api.HandlerImport, Auth: true, API: true, Method: "POST"},
		"/api/export":    {F: api.HandlerExport, Auth: true, API: true, Method: "POST"},
		// "/api/stats"
	}
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		path := "/"
		if r.URL.Path != path {
			path = strings.TrimRight(r.URL.Path, "/")
		}
		start, code, isAPI := time.Now(), http.StatusOK, false
		ctx, cancel := context.WithCancel(mainCtx)
		defer func() {
			cancel()
			switch {
			case code == http.StatusNotFound && !isAPI:
				core.HandlerNotFound(ctx, w, r)
			case code != http.StatusOK && !isAPI:
				core.HandlerError(ctx, w, r)
			case code != http.StatusOK:
				if err := api.HandlerError(w, code); err != nil {
					cfg.L.Error.Println(err)
				}
			}
			cfg.L.Info.Printf("%-5v %v\t%-12v\t%v", r.Method, code, time.Since(start), path)
		}()
		rh, ok := handlers[path]
		if ok {
			isAPI = rh.API
			if (rh.Method != "ANY") && (rh.Method != r.Method) {
				code = http.StatusMethodNotAllowed
				return
			}
			if r.ContentLength > maxSize {
				code = http.StatusRequestEntityTooLarge
				return
			}
			// API accepts only JSON requests
			if ct := r.Header.Get("Content-Type"); isAPI && !strings.HasPrefix(ct, "application/json") {
				code = http.StatusBadRequest
				return
			}
			// pre-authentication: quickly check a token value
			ctx, err := auth.CheckToken(ctx, r, isAPI)
			// anonymous request should be allowed/denied here
			authRequired := rh.Auth || !cfg.Settings.Anonymous
			if err != nil && (authRequired || err != auth.ErrAnonymous) {
				cfg.L.Debug.Printf("authentication error [required=%v]: %v", rh.Auth, err)
				code = http.StatusUnauthorized
				return
			}
			// open new database session
			s, err := db.NewSession(cfg.Conn, true)
			if err != nil {
				cfg.L.Error.Println(err)
				code = http.StatusInternalServerError
				return
			}
			defer s.Close()
			ctx = db.NewContext(ctx, s)
			// authentication
			ctx, err = auth.Authenticate(ctx)
			if err != nil {
				cfg.L.Error.Println(err)
				code = http.StatusUnauthorized
				return
			}
			// call a found handler
			if err := rh.F(ctx, w, r); err.Err != nil {
				cfg.L.Error.Println(err)
				code = err.Status
				return
			}
			return
		} else if link, ok := trim.IsShort(path); ok {
			// it's a short URL candidate
			if r.Method != "GET" {
				code = http.StatusMethodNotAllowed
				return
			}
			origURL, err := core.HandlerRedirect(ctx, link, r)
			switch {
			case err == nil:
				code = http.StatusFound
				http.Redirect(w, r, origURL, code)
			case err == mgo.ErrNotFound:
				code = http.StatusNotFound
			default:
				cfg.L.Error.Println(err)
				code = http.StatusInternalServerError
			}
			return
		}
		code = http.StatusNotFound
	})
	// run server
	go func() {
		errc <- server.ListenAndServe()
	}()
	cfg.L.Info.Printf("%v termination, reason[%v]: %v [%v]\n", Name, <-errc, Version, Revision)
}