##// END OF EJS Templates
lfs: add server side support for the Batch API
Matt Harbison -
r37166:ea6fc585 default
parent child Browse files
Show More
@@ -7,6 +7,10
7 7
8 8 from __future__ import absolute_import
9 9
10 import datetime
11 import errno
12 import json
13
10 14 from mercurial.hgweb import (
11 15 common as hgwebcommon,
12 16 )
@@ -15,6 +19,9 from mercurial import (
15 19 pycompat,
16 20 )
17 21
22 HTTP_OK = hgwebcommon.HTTP_OK
23 HTTP_BAD_REQUEST = hgwebcommon.HTTP_BAD_REQUEST
24
18 25 def handlewsgirequest(orig, rctx, req, res, checkperm):
19 26 """Wrap wireprotoserver.handlewsgirequest() to possibly process an LFS
20 27 request if it is left unprocessed by the wrapped method.
@@ -46,13 +53,177 def handlewsgirequest(orig, rctx, req, r
46 53 res.setbodybytes(b'0\n%s\n' % pycompat.bytestr(e))
47 54 return True
48 55
56 def _sethttperror(res, code, message=None):
57 res.status = hgwebcommon.statusmessage(code, message=message)
58 res.headers[b'Content-Type'] = b'text/plain; charset=utf-8'
59 res.setbodybytes(b'')
60
49 61 def _processbatchrequest(repo, req, res):
50 62 """Handle a request for the Batch API, which is the gateway to granting file
51 63 access.
52 64
53 65 https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md
54 66 """
55 return False
67
68 # Mercurial client request:
69 #
70 # HOST: localhost:$HGPORT
71 # ACCEPT: application/vnd.git-lfs+json
72 # ACCEPT-ENCODING: identity
73 # USER-AGENT: git-lfs/2.3.4 (Mercurial 4.5.2+1114-f48b9754f04c+20180316)
74 # Content-Length: 125
75 # Content-Type: application/vnd.git-lfs+json
76 #
77 # {
78 # "objects": [
79 # {
80 # "oid": "31cf...8e5b"
81 # "size": 12
82 # }
83 # ]
84 # "operation": "upload"
85 # }
86
87 if (req.method != b'POST'
88 or req.headers[b'Content-Type'] != b'application/vnd.git-lfs+json'
89 or req.headers[b'Accept'] != b'application/vnd.git-lfs+json'):
90 # TODO: figure out what the proper handling for a bad request to the
91 # Batch API is.
92 _sethttperror(res, HTTP_BAD_REQUEST, b'Invalid Batch API request')
93 return True
94
95 # XXX: specify an encoding?
96 lfsreq = json.loads(req.bodyfh.read())
97
98 # If no transfer handlers are explicitly requested, 'basic' is assumed.
99 if 'basic' not in lfsreq.get('transfers', ['basic']):
100 _sethttperror(res, HTTP_BAD_REQUEST,
101 b'Only the basic LFS transfer handler is supported')
102 return True
103
104 operation = lfsreq.get('operation')
105 if operation not in ('upload', 'download'):
106 _sethttperror(res, HTTP_BAD_REQUEST,
107 b'Unsupported LFS transfer operation: %s' % operation)
108 return True
109
110 localstore = repo.svfs.lfslocalblobstore
111
112 objects = [p for p in _batchresponseobjects(req, lfsreq.get('objects', []),
113 operation, localstore)]
114
115 rsp = {
116 'transfer': 'basic',
117 'objects': objects,
118 }
119
120 res.status = hgwebcommon.statusmessage(HTTP_OK)
121 res.headers[b'Content-Type'] = b'application/vnd.git-lfs+json'
122 res.setbodybytes(pycompat.bytestr(json.dumps(rsp)))
123
124 return True
125
126 def _batchresponseobjects(req, objects, action, store):
127 """Yield one dictionary of attributes for the Batch API response for each
128 object in the list.
129
130 req: The parsedrequest for the Batch API request
131 objects: The list of objects in the Batch API object request list
132 action: 'upload' or 'download'
133 store: The local blob store for servicing requests"""
134
135 # Successful lfs-test-server response to solict an upload:
136 # {
137 # u'objects': [{
138 # u'size': 12,
139 # u'oid': u'31cf...8e5b',
140 # u'actions': {
141 # u'upload': {
142 # u'href': u'http://localhost:$HGPORT/objects/31cf...8e5b',
143 # u'expires_at': u'0001-01-01T00:00:00Z',
144 # u'header': {
145 # u'Accept': u'application/vnd.git-lfs'
146 # }
147 # }
148 # }
149 # }]
150 # }
151
152 # TODO: Sort out the expires_at/expires_in/authenticated keys.
153
154 for obj in objects:
155 # Convert unicode to ASCII to create a filesystem path
156 oid = obj.get('oid').encode('ascii')
157 rsp = {
158 'oid': oid,
159 'size': obj.get('size'), # XXX: should this check the local size?
160 #'authenticated': True,
161 }
162
163 exists = True
164 verifies = False
165
166 # Verify an existing file on the upload request, so that the client is
167 # solicited to re-upload if it corrupt locally. Download requests are
168 # also verified, so the error can be flagged in the Batch API response.
169 # (Maybe we can use this to short circuit the download for `hg verify`,
170 # IFF the client can assert that the remote end is an hg server.)
171 # Otherwise, it's potentially overkill on download, since it is also
172 # verified as the file is streamed to the caller.
173 try:
174 verifies = store.verify(oid)
175 except IOError as inst:
176 if inst.errno != errno.ENOENT:
177 rsp['error'] = {
178 'code': 500,
179 'message': inst.strerror or 'Internal Server Server'
180 }
181 yield rsp
182 continue
183
184 exists = False
185
186 # Items are always listed for downloads. They are dropped for uploads
187 # IFF they already exist locally.
188 if action == 'download':
189 if not exists:
190 rsp['error'] = {
191 'code': 404,
192 'message': "The object does not exist"
193 }
194 yield rsp
195 continue
196
197 elif not verifies:
198 rsp['error'] = {
199 'code': 422, # XXX: is this the right code?
200 'message': "The object is corrupt"
201 }
202 yield rsp
203 continue
204
205 elif verifies:
206 yield rsp # Skip 'actions': already uploaded
207 continue
208
209 expiresat = datetime.datetime.now() + datetime.timedelta(minutes=10)
210
211 rsp['actions'] = {
212 '%s' % action: {
213 # TODO: Account for the --prefix, if any.
214 'href': '%s/.hg/lfs/objects/%s' % (req.baseurl, oid),
215 # datetime.isoformat() doesn't include the 'Z' suffix
216 "expires_at": expiresat.strftime('%Y-%m-%dT%H:%M:%SZ'),
217 'header': {
218 # The spec doesn't mention the Accept header here, but avoid
219 # a gratuitous deviation from lfs-test-server in the test
220 # output.
221 'Accept': 'application/vnd.git-lfs'
222 }
223 }
224 }
225
226 yield rsp
56 227
57 228 def _processbasictransfer(repo, req, res, checkperm):
58 229 """Handle a single file upload (PUT) or download (GET) action for the Basic
General Comments 0
You need to be logged in to leave comments. Login now