aboutsummaryrefslogtreecommitdiff
path: root/httpd.go
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2022-05-14 13:33:42 +0700
committerShulhan <ms@kilabit.info>2022-05-14 14:03:15 +0700
commitfc042afcacfc105f354b8903837bf22a7169d12e (patch)
treed705a502ba61ba827663eafda99c4497c796c046 /httpd.go
parente4581728e5c6fd9287e2e60aa908b89cbf6feeaf (diff)
downloadrescached-fc042afcacfc105f354b8903837bf22a7169d12e.tar.xz
all: refactoring HTTP API for adding new zone.d record
Previously, the request for adding new record on zone file is by passing the zone name and type inside the path, /zone.d/:name/rr/:type This commit changes it to pass all parameters inside the request body as JSON, { "zone": <string>, "kind": <string>, "record": <base64 string|base64 JSON> } For example, to add A record for subdomain "www" to zone file "my.zone", the request format would be, { "zone": "my.zone", "kind": "A", "record": "eyJOYW1lIjoid3d3IiwiVmFsdWUiOiIxMjcuMC4wLjEifQ==" } Where "record" value is equal to `{"Name":"www","Value":"127.0.0.1"}`. On the response, we changes it to return only the new record instead of all record in the zone.
Diffstat (limited to 'httpd.go')
-rw-r--r--httpd.go107
1 files changed, 76 insertions, 31 deletions
diff --git a/httpd.go b/httpd.go
index f84865b..ff1c866 100644
--- a/httpd.go
+++ b/httpd.go
@@ -4,6 +4,7 @@
package rescached
import (
+ "encoding/base64"
"encoding/json"
"fmt"
"log"
@@ -39,8 +40,8 @@ const (
apiHostsd = "/api/hosts.d"
apiHostsdRR = "/api/hosts.d/rr"
- apiZoned = "/api/zone.d"
- apiZonedRRType = "/api/zone.d/:name/rr/:type"
+ apiZoned = "/api/zone.d"
+ apiZonedRR = "/api/zone.d/rr"
)
func (srv *Server) httpdInit() (err error) {
@@ -242,17 +243,17 @@ func (srv *Server) httpdRegisterEndpoints() (err error) {
err = srv.httpd.RegisterEndpoint(&libhttp.Endpoint{
Method: libhttp.RequestMethodPost,
- Path: apiZonedRRType,
+ Path: apiZonedRR,
RequestType: libhttp.RequestTypeJSON,
ResponseType: libhttp.ResponseTypeJSON,
- Call: srv.apiZonedRRCreate,
+ Call: srv.apiZonedRRAdd,
})
if err != nil {
return err
}
err = srv.httpd.RegisterEndpoint(&libhttp.Endpoint{
Method: libhttp.RequestMethodDelete,
- Path: apiZonedRRType,
+ Path: apiZonedRR,
RequestType: libhttp.RequestTypeJSON,
ResponseType: libhttp.ResponseTypeJSON,
Call: srv.apiZonedRRDelete,
@@ -1049,48 +1050,97 @@ func (srv *Server) apiZonedDelete(epr *libhttp.EndpointRequest) (resb []byte, er
return json.Marshal(&res)
}
-// apiZonedRRCreate create new RR for the zone file.
-func (srv *Server) apiZonedRRCreate(epr *libhttp.EndpointRequest) (resb []byte, err error) {
+type zoneRecordRequest struct {
+ Zone string `json:"zone"`
+ Type string `json:"type"`
+ Record string `json:"record"`
+ recordRaw []byte
+ rtype dns.RecordType
+}
+
+// apiZonedRRAdd create new RR for the zone file.
+//
+// # Request
+//
+// POST /zone.d/rr
+// Content-Type: application/json
+//
+// {
+// "zone": <string>,
+// "type": <string>,
+// "record": <base64 string|base64 JSON>
+// }
+//
+// For example, to add A record for subdomain "www" to zone file "my.zone",
+// the request format would be,
+//
+// {
+// "zone": "my.zone",
+// "type": "A",
+// "record": "eyJOYW1lIjoid3d3IiwiVmFsdWUiOiIxMjcuMC4wLjEifQ=="
+// }
+//
+// Where "record" value is equal to `{"Name":"www","Value":"127.0.0.1"}`.
+//
+// # Response
+//
+// On success, it will return the record being added to the zone file, in the
+// JSON format.
+func (srv *Server) apiZonedRRAdd(epr *libhttp.EndpointRequest) (resb []byte, err error) {
var (
- res = libhttp.EndpointResponse{}
- zoneFileName = epr.HttpRequest.Form.Get(paramNameName)
- rrTypeValue = epr.HttpRequest.Form.Get(paramNameType)
+ res = libhttp.EndpointResponse{}
+ req = zoneRecordRequest{}
zoneFile *dns.Zone
rr *dns.ResourceRecord
- v string
listRR []*dns.ResourceRecord
- rrType int
+ rrValue string
+ ok bool
)
res.Code = http.StatusBadRequest
- if len(zoneFileName) == 0 {
+ err = json.Unmarshal(epr.RequestBody, &req)
+ if err != nil {
+ res.Message = fmt.Sprintf("invalid request: %s", err.Error())
+ return nil, &res
+ }
+
+ if len(req.Zone) == 0 {
res.Message = "empty or invalid zone file name"
return nil, &res
}
- zoneFile = srv.env.Zones[zoneFileName]
+ zoneFile = srv.env.Zones[req.Zone]
if zoneFile == nil {
- res.Message = "unknown zone file name " + zoneFileName
+ res.Message = "unknown zone file name: " + req.Zone
return nil, &res
}
- rrType, err = strconv.Atoi(rrTypeValue)
+ req.Type = strings.ToUpper(req.Type)
+ req.rtype, ok = dns.RecordTypes[req.Type]
+ if !ok {
+ res.Message = fmt.Sprintf("invalid or empty RR type %q: %s", req.Type, err.Error())
+ return nil, &res
+ }
+
+ req.recordRaw, err = base64.StdEncoding.DecodeString(req.Record)
if err != nil {
- res.Message = fmt.Sprintf("invalid or empty RR type %q: %s",
- rrTypeValue, err.Error())
+ res.Message = fmt.Sprintf("invalid record value: %s", err.Error())
return nil, &res
}
rr = &dns.ResourceRecord{}
- switch dns.RecordType(rrType) {
+ switch req.rtype {
case dns.RecordTypeSOA:
rr.Value = &dns.RDataSOA{}
case dns.RecordTypeMX:
rr.Value = &dns.RDataMX{}
+ default:
+ rr.Value = rrValue
}
- err = json.Unmarshal(epr.RequestBody, rr)
+
+ err = json.Unmarshal(req.recordRaw, rr)
if err != nil {
res.Message = "json.Unmarshal:" + err.Error()
return nil, &res
@@ -1103,17 +1153,16 @@ func (srv *Server) apiZonedRRCreate(epr *libhttp.EndpointRequest) (resb []byte,
res.Message = "empty PTR name"
return nil, &res
}
- v = rr.Value.(string)
- if len(v) == 0 {
- rr.Value = zoneFileName
+ if len(rrValue) == 0 {
+ rr.Value = req.Zone
} else {
- rr.Value = v + "." + zoneFileName
+ rr.Value = rrValue + "." + req.Zone
}
} else {
if len(rr.Name) == 0 {
- rr.Name = zoneFileName
+ rr.Name = req.Zone
} else {
- rr.Name += "." + zoneFileName
+ rr.Name += "." + req.Zone
}
}
@@ -1140,11 +1189,7 @@ func (srv *Server) apiZonedRRCreate(epr *libhttp.EndpointRequest) (resb []byte,
res.Code = http.StatusOK
res.Message = fmt.Sprintf("%s record has been saved", dns.RecordTypeNames[rr.Type])
- if rr.Type == dns.RecordTypeSOA {
- res.Data = rr
- } else {
- res.Data = zoneFile.Records
- }
+ res.Data = rr
return json.Marshal(&res)
}