alice-lg/backend/api.go

368 lines
9.6 KiB
Go
Raw Normal View History

2017-05-18 14:44:50 +02:00
package main
import (
"compress/gzip"
"encoding/json"
"net/http"
2017-06-28 12:42:28 +02:00
"log"
2017-05-18 14:44:50 +02:00
"strings"
2017-06-23 16:11:47 +02:00
"time"
2017-05-18 14:44:50 +02:00
2018-06-19 10:02:16 +02:00
"github.com/alice-lg/alice-lg/backend/api"
2017-05-18 14:44:50 +02:00
"github.com/julienschmidt/httprouter"
)
// Alice LG Rest API
//
// The API provides endpoints for getting
// information from the routeservers / alice datasources.
//
// Endpoints:
//
// Config
// Show /api/config
//
// Routeservers
// List /api/routeservers
// Status /api/routeservers/:id/status
// Neighbours /api/routeservers/:id/neighbours
2017-05-18 18:10:29 +02:00
// Routes /api/routeservers/:id/neighbours/:neighbourId/routes
2017-05-18 14:44:50 +02:00
//
2017-05-23 13:58:58 +02:00
// Querying
// LookupPrefix /api/routeservers/:id/lookup/prefix?q=<prefix>
//
2017-05-18 14:44:50 +02:00
2017-05-18 15:26:22 +02:00
type apiEndpoint func(*http.Request, httprouter.Params) (api.Response, error)
2017-05-18 14:44:50 +02:00
// Wrap handler for access controll, throtteling and compression
func endpoint(wrapped apiEndpoint) httprouter.Handle {
return func(res http.ResponseWriter,
req *http.Request,
params httprouter.Params) {
// Get result from handler
result, err := wrapped(req, params)
if err != nil {
result = api.ErrorResponse{
Error: err.Error(),
}
payload, _ := json.Marshal(result)
http.Error(res, string(payload), http.StatusInternalServerError)
return
}
// Encode json
payload, err := json.Marshal(result)
if err != nil {
msg := "Could not encode result as json"
http.Error(res, msg, http.StatusInternalServerError)
log.Println(err)
log.Println("This is most likely due to an older version of go.")
log.Println("Consider upgrading to golang > 1.8")
return
}
2017-05-18 14:44:50 +02:00
// Set response header
res.Header().Set("Content-Type", "application/json")
// Check if compression is supported
if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
// Compress response
res.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(res)
defer gz.Close()
gz.Write(payload)
} else {
res.Write(payload) // Fall back to uncompressed response
}
}
}
// Register api endpoints
func apiRegisterEndpoints(router *httprouter.Router) error {
2017-05-19 11:32:22 +02:00
// Meta
router.GET("/api/status", endpoint(apiStatusShow))
2017-05-18 14:44:50 +02:00
router.GET("/api/config", endpoint(apiConfigShow))
// Routeservers
2017-05-18 18:10:29 +02:00
router.GET("/api/routeservers",
endpoint(apiRouteserversList))
router.GET("/api/routeservers/:id/status",
endpoint(apiStatus))
router.GET("/api/routeservers/:id/neighbours",
endpoint(apiNeighboursList))
router.GET("/api/routeservers/:id/neighbours/:neighbourId/routes",
endpoint(apiRoutesList))
2018-07-13 16:45:15 +02:00
router.GET("/api/routeservers/:id/neighbours/:neighbourId/routes/received",
endpoint(apiRoutesListReceived))
router.GET("/api/routeservers/:id/neighbours/:neighbourId/routes/filtered",
endpoint(apiRoutesListFiltered))
router.GET("/api/routeservers/:id/neighbours/:neighbourId/routes/not-exported",
endpoint(apiRoutesListNotExported))
2017-05-18 14:44:50 +02:00
2017-05-23 13:58:58 +02:00
// Querying
2017-07-04 12:36:48 +02:00
if AliceConfig.Server.EnablePrefixLookup == true {
router.GET("/api/lookup/prefix",
endpoint(apiLookupPrefixGlobal))
}
2017-06-19 16:44:24 +02:00
2017-05-18 14:44:50 +02:00
return nil
}
2017-05-19 11:32:22 +02:00
// Handle Status Endpoint, this is intended for
// monitoring and service health checks
func apiStatusShow(_req *http.Request, _params httprouter.Params) (api.Response, error) {
status, err := NewAppStatus()
return status, err
}
2017-05-18 14:44:50 +02:00
// Handle Config Endpoint
2017-05-18 15:26:22 +02:00
func apiConfigShow(_req *http.Request, _params httprouter.Params) (api.Response, error) {
2017-05-18 15:23:36 +02:00
result := api.ConfigResponse{
Rejection: api.Rejection{
Asn: AliceConfig.Ui.RoutesRejections.Asn,
RejectId: AliceConfig.Ui.RoutesRejections.RejectId,
},
2017-07-10 14:19:36 +01:00
RejectReasons: SerializeReasons(
AliceConfig.Ui.RoutesRejections.Reasons),
2017-05-18 15:23:36 +02:00
Noexport: api.Noexport{
Asn: AliceConfig.Ui.RoutesNoexports.Asn,
NoexportId: AliceConfig.Ui.RoutesNoexports.NoexportId,
LoadOnDemand: AliceConfig.Ui.RoutesNoexports.LoadOnDemand,
2017-05-18 15:23:36 +02:00
},
2017-07-10 14:19:36 +01:00
NoexportReasons: SerializeReasons(
AliceConfig.Ui.RoutesNoexports.Reasons),
2018-07-05 10:18:17 +02:00
RoutesColumns: AliceConfig.Ui.RoutesColumns,
RoutesColumnsOrder: AliceConfig.Ui.RoutesColumnsOrder,
NeighboursColumns: AliceConfig.Ui.NeighboursColumns,
NeighboursColumnsOrder: AliceConfig.Ui.NeighboursColumnsOrder,
2018-08-03 10:37:05 +02:00
LookupColumns: AliceConfig.Ui.LookupColumns,
LookupColumnsOrder: AliceConfig.Ui.LookupColumnsOrder,
2018-07-05 10:18:17 +02:00
PrefixLookupEnabled: AliceConfig.Server.EnablePrefixLookup,
2017-05-18 15:23:36 +02:00
}
return result, nil
2017-05-18 14:44:50 +02:00
}
2017-05-18 15:26:22 +02:00
// Handle Routeservers List
func apiRouteserversList(_req *http.Request, _params httprouter.Params) (api.Response, error) {
2017-05-18 17:35:27 +02:00
// Get list of sources from config,
routeservers := []api.Routeserver{}
sources := AliceConfig.Sources
2017-06-26 16:41:50 +02:00
for _, source := range sources {
2017-05-18 17:35:27 +02:00
routeservers = append(routeservers, api.Routeserver{
2017-06-26 16:41:50 +02:00
Id: source.Id,
2017-05-18 17:35:27 +02:00
Name: source.Name,
})
}
// Make routeservers response
response := api.RouteserversResponse{
Routeservers: routeservers,
}
return response, nil
2017-05-18 15:26:22 +02:00
}
2017-05-18 18:10:29 +02:00
// Handle status
func apiStatus(_req *http.Request, params httprouter.Params) (api.Response, error) {
2017-05-18 18:22:52 +02:00
rsId, err := validateSourceId(params.ByName("id"))
if err != nil {
return nil, err
}
2017-05-18 18:10:29 +02:00
source := AliceConfig.Sources[rsId].getInstance()
result, err := source.Status()
return result, err
}
// Handle get neighbours on routeserver
func apiNeighboursList(_req *http.Request, params httprouter.Params) (api.Response, error) {
2017-05-18 18:22:52 +02:00
rsId, err := validateSourceId(params.ByName("id"))
if err != nil {
return nil, err
}
2017-05-18 18:10:29 +02:00
source := AliceConfig.Sources[rsId].getInstance()
result, err := source.Neighbours()
return result, err
}
// Handle routes
func apiRoutesList(_req *http.Request, params httprouter.Params) (api.Response, error) {
2017-05-18 18:22:52 +02:00
rsId, err := validateSourceId(params.ByName("id"))
if err != nil {
return nil, err
}
2017-05-18 18:10:29 +02:00
neighbourId := params.ByName("neighbourId")
source := AliceConfig.Sources[rsId].getInstance()
result, err := source.Routes(neighbourId)
return result, err
}
2017-05-23 13:58:58 +02:00
2018-07-13 16:45:15 +02:00
// Paginated Routes Respponse: Received routes
func apiRoutesListReceived(
req *http.Request,
params httprouter.Params,
) (api.Response, error) {
rsId, err := validateSourceId(params.ByName("id"))
if err != nil {
return nil, err
}
neighbourId := params.ByName("neighbourId")
source := AliceConfig.Sources[rsId].getInstance()
result, err := source.RoutesReceived(neighbourId)
if err != nil {
return nil, err
}
2018-07-15 16:55:26 +02:00
// Filter routes based on criteria if present
routes := apiQueryFilterNextHopGateway(req, "q", result.Imported)
2018-07-13 16:45:15 +02:00
// Paginate results
page := apiQueryMustInt(req, "page", 0)
2018-07-25 15:36:11 +02:00
pageSize := AliceConfig.Ui.Pagination.RoutesAcceptedPageSize
2018-07-15 16:55:26 +02:00
routes, pagination := apiPaginateRoutes(routes, page, pageSize)
2018-07-13 16:45:15 +02:00
// Make paginated response
response := api.PaginatedRoutesResponse{
RoutesResponse: &api.RoutesResponse{
Api: result.Api,
Imported: routes,
},
Pagination: pagination,
}
return response, nil
}
func apiRoutesListFiltered(
req *http.Request,
params httprouter.Params,
) (api.Response, error) {
rsId, err := validateSourceId(params.ByName("id"))
if err != nil {
return nil, err
}
neighbourId := params.ByName("neighbourId")
source := AliceConfig.Sources[rsId].getInstance()
result, err := source.RoutesFiltered(neighbourId)
if err != nil {
return nil, err
}
2018-07-15 16:55:26 +02:00
// Filter routes based on criteria if present
routes := apiQueryFilterNextHopGateway(req, "q", result.Filtered)
2018-07-13 16:45:15 +02:00
// Paginate results
page := apiQueryMustInt(req, "page", 0)
2018-07-25 15:36:11 +02:00
pageSize := AliceConfig.Ui.Pagination.RoutesFilteredPageSize
2018-07-15 16:55:26 +02:00
routes, pagination := apiPaginateRoutes(routes, page, pageSize)
2018-07-13 16:45:15 +02:00
// Make response
response := api.PaginatedRoutesResponse{
RoutesResponse: &api.RoutesResponse{
Api: result.Api,
Filtered: routes,
},
Pagination: pagination,
}
return response, nil
}
func apiRoutesListNotExported(
req *http.Request,
params httprouter.Params,
) (api.Response, error) {
rsId, err := validateSourceId(params.ByName("id"))
if err != nil {
return nil, err
}
neighbourId := params.ByName("neighbourId")
source := AliceConfig.Sources[rsId].getInstance()
result, err := source.RoutesNotExported(neighbourId)
if err != nil {
return nil, err
}
2018-07-15 16:55:26 +02:00
routes := apiQueryFilterNextHopGateway(req, "q", result.NotExported)
2018-07-13 16:45:15 +02:00
// Paginate results
page := apiQueryMustInt(req, "page", 0)
2018-07-25 15:36:11 +02:00
pageSize := AliceConfig.Ui.Pagination.RoutesNotExportedPageSize
2018-07-15 16:55:26 +02:00
routes, pagination := apiPaginateRoutes(routes, page, pageSize)
2018-07-13 16:45:15 +02:00
// Make response
response := api.PaginatedRoutesResponse{
RoutesResponse: &api.RoutesResponse{
Api: result.Api,
NotExported: routes,
},
Pagination: pagination,
}
return response, nil
}
2017-06-19 16:44:24 +02:00
// Handle global lookup
func apiLookupPrefixGlobal(req *http.Request, params httprouter.Params) (api.Response, error) {
// Get prefix to query
2017-06-30 14:15:43 +02:00
q, err := validateQueryString(req, "q")
2017-06-23 16:11:47 +02:00
if err != nil {
return nil, err
}
2017-06-19 16:44:24 +02:00
2017-06-30 14:15:43 +02:00
q, err = validatePrefixQuery(q)
2017-06-26 12:42:49 +02:00
if err != nil {
return nil, err
}
2017-06-28 13:16:10 +02:00
// Get pagination params
2017-06-28 16:31:03 +02:00
limit, offset, err := validatePaginationParams(req, 50, 0)
2017-06-28 13:16:10 +02:00
if err != nil {
return nil, err
}
2017-06-30 11:12:15 +02:00
// Check what we want to query
// Prefix -> fetch prefix
// _ -> fetch neighbours and routes
2017-06-30 14:15:43 +02:00
lookupPrefix := MaybePrefix(q)
2017-06-30 11:12:15 +02:00
2017-06-30 14:15:43 +02:00
// Measure response time
2017-06-23 16:11:47 +02:00
t0 := time.Now()
2017-06-30 14:15:43 +02:00
// Perform query
2018-07-07 11:45:34 +02:00
var routes api.LookupRoutes
2017-06-30 14:15:43 +02:00
if lookupPrefix {
routes = AliceRoutesStore.LookupPrefix(q)
} else {
neighbours := AliceNeighboursStore.LookupNeighbours(q)
routes = AliceRoutesStore.LookupPrefixForNeighbours(neighbours)
}
2017-06-23 10:46:09 +02:00
2017-06-28 13:16:10 +02:00
// Paginate result
totalRoutes := len(routes)
cap := offset + limit
if cap > totalRoutes {
cap = totalRoutes
}
2017-06-23 16:11:47 +02:00
queryDuration := time.Since(t0)
response := api.RoutesLookupResponseGlobal{
2017-06-28 13:16:10 +02:00
Routes: routes[offset:cap],
TotalRoutes: totalRoutes,
Limit: limit,
Offset: offset,
Time: float64(queryDuration) / 1000.0 / 1000.0, // nano -> micro -> milli
2017-06-23 16:11:47 +02:00
}
2017-06-19 16:44:24 +02:00
return response, nil
}