github.com/cayleygraph/cayley@v0.7.7/internal/http/write.go (about) 1 // Copyright 2014 The Cayley Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package http 16 17 import ( 18 "encoding/json" 19 "errors" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "net/http" 24 "strconv" 25 26 "github.com/julienschmidt/httprouter" 27 28 "github.com/cayleygraph/cayley/clog" 29 "github.com/cayleygraph/cayley/graph" 30 "github.com/cayleygraph/cayley/internal/decompressor" 31 "github.com/cayleygraph/quad" 32 "github.com/cayleygraph/quad/nquads" 33 ) 34 35 func ParseJSONToQuadList(jsonBody []byte) (out []quad.Quad, _ error) { 36 var quads []struct { 37 Subject string `json:"subject"` 38 Predicate string `json:"predicate"` 39 Object string `json:"object"` 40 Label string `json:"label"` 41 } 42 err := json.Unmarshal(jsonBody, &quads) 43 if err != nil { 44 return nil, err 45 } 46 out = make([]quad.Quad, 0, len(quads)) 47 for i, jq := range quads { 48 q := quad.Quad{ 49 Subject: quad.StringToValue(jq.Subject), 50 Predicate: quad.StringToValue(jq.Predicate), 51 Object: quad.StringToValue(jq.Object), 52 Label: quad.StringToValue(jq.Label), 53 } 54 if !q.IsValid() { 55 return nil, fmt.Errorf("invalid quad at index %d. %s", i, q) 56 } 57 out = append(out, q) 58 } 59 return out, nil 60 } 61 62 const maxQuerySize = 1024 * 1024 // 1 MB 63 func readLimit(r io.Reader) ([]byte, error) { 64 lr := io.LimitReader(r, maxQuerySize).(*io.LimitedReader) 65 data, err := ioutil.ReadAll(lr) 66 if err != nil && lr.N <= 0 { 67 err = errors.New("request is too large") 68 } 69 return data, err 70 } 71 72 func (api *API) ServeV1Write(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 73 if api.config.ReadOnly { 74 jsonResponse(w, 400, "Database is read-only.") 75 return 76 } 77 // TODO: streaming reader 78 bodyBytes, err := readLimit(r.Body) 79 if err != nil { 80 jsonResponse(w, 400, err) 81 return 82 } 83 quads, err := ParseJSONToQuadList(bodyBytes) 84 if err != nil { 85 jsonResponse(w, 400, err) 86 return 87 } 88 h, err := api.GetHandleForRequest(r) 89 if err != nil { 90 jsonResponse(w, 400, err) 91 return 92 } 93 if err = h.QuadWriter.AddQuadSet(quads); err != nil { 94 jsonResponse(w, 400, err) 95 return 96 } 97 fmt.Fprintf(w, "{\"result\": \"Successfully wrote %d quads.\"}", len(quads)) 98 } 99 100 func (api *API) ServeV1WriteNQuad(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 101 if api.config.ReadOnly { 102 jsonResponse(w, 400, "Database is read-only.") 103 return 104 } 105 106 formFile, _, err := r.FormFile("NQuadFile") 107 if err != nil { 108 clog.Errorf("%v", err) 109 jsonResponse(w, 500, "Couldn't read file: "+err.Error()) 110 return 111 } 112 defer formFile.Close() 113 114 blockSize, blockErr := strconv.Atoi(r.URL.Query().Get("block_size")) 115 if blockErr != nil { 116 blockSize = quad.DefaultBatch 117 } 118 119 quadReader, err := decompressor.New(formFile) 120 // TODO(kortschak) Make this configurable from the web UI. 121 dec := nquads.NewReader(quadReader, false) 122 123 h, err := api.GetHandleForRequest(r) 124 if err != nil { 125 jsonResponse(w, 400, err) 126 return 127 } 128 qw := graph.NewWriter(h.QuadWriter) 129 n, err := quad.CopyBatch(qw, dec, blockSize) 130 if err != nil { 131 jsonResponse(w, 400, err) 132 return 133 } 134 err = qw.Close() 135 if err != nil { 136 jsonResponse(w, 400, err) 137 return 138 } 139 fmt.Fprintf(w, "{\"result\": \"Successfully wrote %d quads.\"}", n) 140 } 141 142 func (api *API) ServeV1Delete(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 143 if api.config.ReadOnly { 144 jsonResponse(w, 400, "Database is read-only.") 145 return 146 } 147 bodyBytes, err := readLimit(r.Body) 148 if err != nil { 149 jsonResponse(w, 400, err) 150 return 151 } 152 quads, err := ParseJSONToQuadList(bodyBytes) 153 if err != nil { 154 jsonResponse(w, 400, err) 155 return 156 } 157 h, err := api.GetHandleForRequest(r) 158 if err != nil { 159 jsonResponse(w, 400, err) 160 return 161 } 162 for _, q := range quads { 163 err = h.QuadWriter.RemoveQuad(q) 164 if err != nil && !graph.IsQuadNotExist(err) { 165 jsonResponse(w, 400, err) 166 return 167 } 168 } 169 fmt.Fprintf(w, "{\"result\": \"Successfully deleted %d quads.\"}", len(quads)) 170 }