001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package org.apache.hadoop.fs.http.server;
020    
021    import org.apache.hadoop.classification.InterfaceAudience;
022    import org.apache.hadoop.conf.Configuration;
023    import org.apache.hadoop.fs.FileSystem;
024    import org.apache.hadoop.fs.http.client.HttpFSFileSystem;
025    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OperationParam;
026    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.AccessTimeParam;
027    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.BlockSizeParam;
028    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.DataParam;
029    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.RecursiveParam;
030    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.DoAsParam;
031    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.FilterParam;
032    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.GroupParam;
033    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.LenParam;
034    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.ModifiedTimeParam;
035    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OffsetParam;
036    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OverwriteParam;
037    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OwnerParam;
038    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.PermissionParam;
039    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.ReplicationParam;
040    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.DestinationParam;
041    import org.apache.hadoop.lib.service.FileSystemAccess;
042    import org.apache.hadoop.lib.service.FileSystemAccessException;
043    import org.apache.hadoop.lib.service.Groups;
044    import org.apache.hadoop.lib.service.Instrumentation;
045    import org.apache.hadoop.lib.service.ProxyUser;
046    import org.apache.hadoop.lib.servlet.FileSystemReleaseFilter;
047    import org.apache.hadoop.lib.servlet.HostnameFilter;
048    import org.apache.hadoop.lib.wsrs.InputStreamEntity;
049    import org.apache.hadoop.lib.wsrs.Parameters;
050    import org.apache.hadoop.security.authentication.server.AuthenticationToken;
051    import org.json.simple.JSONObject;
052    import org.slf4j.Logger;
053    import org.slf4j.LoggerFactory;
054    import org.slf4j.MDC;
055    
056    import javax.ws.rs.Consumes;
057    import javax.ws.rs.DELETE;
058    import javax.ws.rs.GET;
059    import javax.ws.rs.POST;
060    import javax.ws.rs.PUT;
061    import javax.ws.rs.Path;
062    import javax.ws.rs.PathParam;
063    import javax.ws.rs.Produces;
064    import javax.ws.rs.QueryParam;
065    import javax.ws.rs.core.Context;
066    import javax.ws.rs.core.MediaType;
067    import javax.ws.rs.core.Response;
068    import javax.ws.rs.core.UriBuilder;
069    import javax.ws.rs.core.UriInfo;
070    import java.io.IOException;
071    import java.io.InputStream;
072    import java.net.URI;
073    import java.security.AccessControlException;
074    import java.security.Principal;
075    import java.text.MessageFormat;
076    import java.util.List;
077    import java.util.Map;
078    
079    /**
080     * Main class of HttpFSServer server.
081     * <p/>
082     * The <code>HttpFSServer</code> class uses Jersey JAX-RS to binds HTTP requests to the
083     * different operations.
084     */
085    @Path(HttpFSFileSystem.SERVICE_VERSION)
086    @InterfaceAudience.Private
087    public class HttpFSServer {
088      private static Logger AUDIT_LOG = LoggerFactory.getLogger("httpfsaudit");
089    
090      /**
091       * Resolves the effective user that will be used to request a FileSystemAccess filesystem.
092       * <p/>
093       * If the doAs-user is NULL or the same as the user, it returns the user.
094       * <p/>
095       * Otherwise it uses proxyuser rules (see {@link ProxyUser} to determine if the
096       * current user can impersonate the doAs-user.
097       * <p/>
098       * If the current user cannot impersonate the doAs-user an
099       * <code>AccessControlException</code> will be thrown.
100       *
101       * @param user principal for whom the filesystem instance is.
102       * @param doAs do-as user, if any.
103       *
104       * @return the effective user.
105       *
106       * @throws IOException thrown if an IO error occurrs.
107       * @throws AccessControlException thrown if the current user cannot impersonate
108       * the doAs-user.
109       */
110      private String getEffectiveUser(Principal user, String doAs) throws IOException {
111        String effectiveUser = user.getName();
112        if (doAs != null && !doAs.equals(user.getName())) {
113          ProxyUser proxyUser = HttpFSServerWebApp.get().get(ProxyUser.class);
114          String proxyUserName;
115          if (user instanceof AuthenticationToken) {
116            proxyUserName = ((AuthenticationToken)user).getUserName();
117          } else {
118            proxyUserName = user.getName();
119          }
120          proxyUser.validate(proxyUserName, HostnameFilter.get(), doAs);
121          effectiveUser = doAs;
122          AUDIT_LOG.info("Proxy user [{}] DoAs user [{}]", proxyUserName, doAs);
123        }
124        return effectiveUser;
125      }
126    
127      /**
128       * Executes a {@link FileSystemAccess.FileSystemExecutor} using a filesystem for the effective
129       * user.
130       *
131       * @param user principal making the request.
132       * @param doAs do-as user, if any.
133       * @param executor FileSystemExecutor to execute.
134       *
135       * @return FileSystemExecutor response
136       *
137       * @throws IOException thrown if an IO error occurrs.
138       * @throws FileSystemAccessException thrown if a FileSystemAccess releated error occurred. Thrown
139       * exceptions are handled by {@link HttpFSExceptionProvider}.
140       */
141      private <T> T fsExecute(Principal user, String doAs, FileSystemAccess.FileSystemExecutor<T> executor)
142        throws IOException, FileSystemAccessException {
143        String hadoopUser = getEffectiveUser(user, doAs);
144        FileSystemAccess fsAccess = HttpFSServerWebApp.get().get(FileSystemAccess.class);
145        Configuration conf = HttpFSServerWebApp.get().get(FileSystemAccess.class).getFileSystemConfiguration();
146        return fsAccess.execute(hadoopUser, conf, executor);
147      }
148    
149      /**
150       * Returns a filesystem instance. The fileystem instance is wired for release at the completion of
151       * the current Servlet request via the {@link FileSystemReleaseFilter}.
152       * <p/>
153       * If a do-as user is specified, the current user must be a valid proxyuser, otherwise an
154       * <code>AccessControlException</code> will be thrown.
155       *
156       * @param user principal for whom the filesystem instance is.
157       * @param doAs do-as user, if any.
158       *
159       * @return a filesystem for the specified user or do-as user.
160       *
161       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
162       * handled by {@link HttpFSExceptionProvider}.
163       * @throws FileSystemAccessException thrown if a FileSystemAccess releated error occurred. Thrown
164       * exceptions are handled by {@link HttpFSExceptionProvider}.
165       */
166      private FileSystem createFileSystem(Principal user, String doAs) throws IOException, FileSystemAccessException {
167        String hadoopUser = getEffectiveUser(user, doAs);
168        FileSystemAccess fsAccess = HttpFSServerWebApp.get().get(FileSystemAccess.class);
169        Configuration conf = HttpFSServerWebApp.get().get(FileSystemAccess.class).getFileSystemConfiguration();
170        FileSystem fs = fsAccess.createFileSystem(hadoopUser, conf);
171        FileSystemReleaseFilter.setFileSystem(fs);
172        return fs;
173      }
174    
175      private void enforceRootPath(HttpFSFileSystem.Operation op, String path) {
176        if (!path.equals("/")) {
177          throw new UnsupportedOperationException(
178            MessageFormat.format("Operation [{0}], invalid path [{1}], must be '/'",
179                                 op, path));
180        }
181      }
182    
183      /**
184       * Special binding for '/' as it is not handled by the wildcard binding.
185       *
186       * @param user the principal of the user making the request.
187       * @param op the HttpFS operation of the request.
188       * @param params the HttpFS parameters of the request.
189       *
190       * @return the request response.
191       *
192       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
193       * handled by {@link HttpFSExceptionProvider}.
194       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
195       * error occurred. Thrown exceptions are handled by
196       * {@link HttpFSExceptionProvider}.
197       */
198      @GET
199      @Path("/")
200      @Produces(MediaType.APPLICATION_JSON)
201      public Response getRoot(@Context Principal user,
202                              @QueryParam(OperationParam.NAME) OperationParam op,
203                              @Context Parameters params)
204        throws IOException, FileSystemAccessException {
205        return get(user, "", op, params);
206      }
207    
208      private String makeAbsolute(String path) {
209        return "/" + ((path != null) ? path : "");
210      }
211    
212      /**
213       * Binding to handle GET requests, supported operations are
214       *
215       * @param user the principal of the user making the request.
216       * @param path the path for operation.
217       * @param op the HttpFS operation of the request.
218       * @param params the HttpFS parameters of the request.
219       *
220       * @return the request response.
221       *
222       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
223       * handled by {@link HttpFSExceptionProvider}.
224       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
225       * error occurred. Thrown exceptions are handled by
226       * {@link HttpFSExceptionProvider}.
227       */
228      @GET
229      @Path("{path:.*}")
230      @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
231      public Response get(@Context Principal user,
232                          @PathParam("path") String path,
233                          @QueryParam(OperationParam.NAME) OperationParam op,
234                          @Context Parameters params)
235        throws IOException, FileSystemAccessException {
236        Response response;
237        path = makeAbsolute(path);
238        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
239        String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
240        switch (op.value()) {
241          case OPEN: {
242            //Invoking the command directly using an unmanaged FileSystem that is
243            // released by the FileSystemReleaseFilter
244            FSOperations.FSOpen command = new FSOperations.FSOpen(path);
245            FileSystem fs = createFileSystem(user, doAs);
246            InputStream is = command.execute(fs);
247            Long offset = params.get(OffsetParam.NAME, OffsetParam.class);
248            Long len = params.get(LenParam.NAME, LenParam.class);
249            AUDIT_LOG.info("[{}] offset [{}] len [{}]",
250                           new Object[]{path, offset, len});
251            InputStreamEntity entity = new InputStreamEntity(is, offset, len);
252            response =
253              Response.ok(entity).type(MediaType.APPLICATION_OCTET_STREAM).build();
254            break;
255          }
256          case GETFILESTATUS: {
257            FSOperations.FSFileStatus command =
258              new FSOperations.FSFileStatus(path);
259            Map json = fsExecute(user, doAs, command);
260            AUDIT_LOG.info("[{}]", path);
261            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
262            break;
263          }
264          case LISTSTATUS: {
265            String filter = params.get(FilterParam.NAME, FilterParam.class);
266            FSOperations.FSListStatus command = new FSOperations.FSListStatus(
267              path, filter);
268            Map json = fsExecute(user, doAs, command);
269            AUDIT_LOG.info("[{}] filter [{}]", path,
270                           (filter != null) ? filter : "-");
271            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
272            break;
273          }
274          case GETHOMEDIRECTORY: {
275            enforceRootPath(op.value(), path);
276            FSOperations.FSHomeDir command = new FSOperations.FSHomeDir();
277            JSONObject json = fsExecute(user, doAs, command);
278            AUDIT_LOG.info("");
279            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
280            break;
281          }
282          case INSTRUMENTATION: {
283            enforceRootPath(op.value(), path);
284            Groups groups = HttpFSServerWebApp.get().get(Groups.class);
285            List<String> userGroups = groups.getGroups(user.getName());
286            if (!userGroups.contains(HttpFSServerWebApp.get().getAdminGroup())) {
287              throw new AccessControlException(
288                "User not in HttpFSServer admin group");
289            }
290            Instrumentation instrumentation =
291              HttpFSServerWebApp.get().get(Instrumentation.class);
292            Map snapshot = instrumentation.getSnapshot();
293            response = Response.ok(snapshot).build();
294            break;
295          }
296          case GETCONTENTSUMMARY: {
297            FSOperations.FSContentSummary command =
298              new FSOperations.FSContentSummary(path);
299            Map json = fsExecute(user, doAs, command);
300            AUDIT_LOG.info("[{}]", path);
301            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
302            break;
303          }
304          case GETFILECHECKSUM: {
305            FSOperations.FSFileChecksum command =
306              new FSOperations.FSFileChecksum(path);
307            Map json = fsExecute(user, doAs, command);
308            AUDIT_LOG.info("[{}]", path);
309            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
310            break;
311          }
312          case GETFILEBLOCKLOCATIONS: {
313            response = Response.status(Response.Status.BAD_REQUEST).build();
314            break;
315          }
316          default: {
317            throw new IOException(
318              MessageFormat.format("Invalid HTTP GET operation [{0}]",
319                                   op.value()));
320          }
321        }
322        return response;
323      }
324    
325    
326      /**
327       * Binding to handle DELETE requests.
328       *
329       * @param user the principal of the user making the request.
330       * @param path the path for operation.
331       * @param op the HttpFS operation of the request.
332       * @param params the HttpFS parameters of the request.
333       *
334       * @return the request response.
335       *
336       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
337       * handled by {@link HttpFSExceptionProvider}.
338       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
339       * error occurred. Thrown exceptions are handled by
340       * {@link HttpFSExceptionProvider}.
341       */
342      @DELETE
343      @Path("{path:.*}")
344      @Produces(MediaType.APPLICATION_JSON)
345      public Response delete(@Context Principal user,
346                          @PathParam("path") String path,
347                          @QueryParam(OperationParam.NAME) OperationParam op,
348                          @Context Parameters params)
349        throws IOException, FileSystemAccessException {
350        Response response;
351        path = makeAbsolute(path);
352        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
353        String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
354        switch (op.value()) {
355          case DELETE: {
356            Boolean recursive =
357              params.get(RecursiveParam.NAME,  RecursiveParam.class);
358            AUDIT_LOG.info("[{}] recursive [{}]", path, recursive);
359            FSOperations.FSDelete command =
360              new FSOperations.FSDelete(path, recursive);
361            JSONObject json = fsExecute(user, doAs, command);
362            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
363            break;
364          }
365          default: {
366            throw new IOException(
367              MessageFormat.format("Invalid HTTP DELETE operation [{0}]",
368                                   op.value()));
369          }
370        }
371        return response;
372      }
373    
374      /**
375       * Binding to handle POST requests.
376       *
377       * @param is the inputstream for the request payload.
378       * @param user the principal of the user making the request.
379       * @param uriInfo the of the request.
380       * @param path the path for operation.
381       * @param op the HttpFS operation of the request.
382       * @param params the HttpFS parameters of the request.
383       *
384       * @return the request response.
385       *
386       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
387       * handled by {@link HttpFSExceptionProvider}.
388       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
389       * error occurred. Thrown exceptions are handled by
390       * {@link HttpFSExceptionProvider}.
391       */
392      @POST
393      @Path("{path:.*}")
394      @Consumes({"*/*"})
395      @Produces({MediaType.APPLICATION_JSON})
396      public Response post(InputStream is,
397                           @Context Principal user,
398                           @Context UriInfo uriInfo,
399                           @PathParam("path") String path,
400                           @QueryParam(OperationParam.NAME) OperationParam op,
401                           @Context Parameters params)
402        throws IOException, FileSystemAccessException {
403        Response response;
404        path = makeAbsolute(path);
405        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
406        String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
407        switch (op.value()) {
408          case APPEND: {
409            Boolean hasData = params.get(DataParam.NAME, DataParam.class);
410            if (!hasData) {
411              response = Response.temporaryRedirect(
412                createUploadRedirectionURL(uriInfo,
413                  HttpFSFileSystem.Operation.APPEND)).build();
414            } else {
415              FSOperations.FSAppend command =
416                new FSOperations.FSAppend(is, path);
417              fsExecute(user, doAs, command);
418              AUDIT_LOG.info("[{}]", path);
419              response = Response.ok().type(MediaType.APPLICATION_JSON).build();
420            }
421            break;
422          }
423          default: {
424            throw new IOException(
425              MessageFormat.format("Invalid HTTP POST operation [{0}]",
426                                   op.value()));
427          }
428        }
429        return response;
430      }
431    
432      /**
433       * Creates the URL for an upload operation (create or append).
434       *
435       * @param uriInfo uri info of the request.
436       * @param uploadOperation operation for the upload URL.
437       *
438       * @return the URI for uploading data.
439       */
440      protected URI createUploadRedirectionURL(UriInfo uriInfo, Enum<?> uploadOperation) {
441        UriBuilder uriBuilder = uriInfo.getRequestUriBuilder();
442        uriBuilder = uriBuilder.replaceQueryParam(OperationParam.NAME, uploadOperation).
443          queryParam(DataParam.NAME, Boolean.TRUE);
444        return uriBuilder.build(null);
445      }
446    
447    
448      /**
449       * Binding to handle PUT requests.
450       *
451       * @param is the inputstream for the request payload.
452       * @param user the principal of the user making the request.
453       * @param uriInfo the of the request.
454       * @param path the path for operation.
455       * @param op the HttpFS operation of the request.
456       * @param params the HttpFS parameters of the request.
457       *
458       * @return the request response.
459       *
460       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
461       * handled by {@link HttpFSExceptionProvider}.
462       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
463       * error occurred. Thrown exceptions are handled by
464       * {@link HttpFSExceptionProvider}.
465       */
466      @PUT
467      @Path("{path:.*}")
468      @Consumes({"*/*"})
469      @Produces({MediaType.APPLICATION_JSON})
470      public Response put(InputStream is,
471                           @Context Principal user,
472                           @Context UriInfo uriInfo,
473                           @PathParam("path") String path,
474                           @QueryParam(OperationParam.NAME) OperationParam op,
475                           @Context Parameters params)
476        throws IOException, FileSystemAccessException {
477        Response response;
478        path = makeAbsolute(path);
479        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
480        String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
481        switch (op.value()) {
482          case CREATE: {
483            Boolean hasData = params.get(DataParam.NAME, DataParam.class);
484            if (!hasData) {
485              response = Response.temporaryRedirect(
486                createUploadRedirectionURL(uriInfo,
487                  HttpFSFileSystem.Operation.CREATE)).build();
488            } else {
489              Short permission = params.get(PermissionParam.NAME,
490                                             PermissionParam.class);
491              Boolean override = params.get(OverwriteParam.NAME,
492                                            OverwriteParam.class);
493              Short replication = params.get(ReplicationParam.NAME,
494                                             ReplicationParam.class);
495              Long blockSize = params.get(BlockSizeParam.NAME,
496                                          BlockSizeParam.class);
497              FSOperations.FSCreate command =
498                new FSOperations.FSCreate(is, path, permission, override,
499                                          replication, blockSize);
500              fsExecute(user, doAs, command);
501              AUDIT_LOG.info(
502                "[{}] permission [{}] override [{}] replication [{}] blockSize [{}]",
503                new Object[]{path, permission, override, replication, blockSize});
504              response = Response.status(Response.Status.CREATED).build();
505            }
506            break;
507          }
508          case MKDIRS: {
509            Short permission = params.get(PermissionParam.NAME,
510                                           PermissionParam.class);
511            FSOperations.FSMkdirs command =
512              new FSOperations.FSMkdirs(path, permission);
513            JSONObject json = fsExecute(user, doAs, command);
514            AUDIT_LOG.info("[{}] permission [{}]", path, permission);
515            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
516            break;
517          }
518          case RENAME: {
519            String toPath = params.get(DestinationParam.NAME, DestinationParam.class);
520            FSOperations.FSRename command =
521              new FSOperations.FSRename(path, toPath);
522            JSONObject json = fsExecute(user, doAs, command);
523            AUDIT_LOG.info("[{}] to [{}]", path, toPath);
524            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
525            break;
526          }
527          case SETOWNER: {
528            String owner = params.get(OwnerParam.NAME, OwnerParam.class);
529            String group = params.get(GroupParam.NAME, GroupParam.class);
530            FSOperations.FSSetOwner command =
531              new FSOperations.FSSetOwner(path, owner, group);
532            fsExecute(user, doAs, command);
533            AUDIT_LOG.info("[{}] to (O/G)[{}]", path, owner + ":" + group);
534            response = Response.ok().build();
535            break;
536          }
537          case SETPERMISSION: {
538            Short permission = params.get(PermissionParam.NAME,
539                                          PermissionParam.class);
540            FSOperations.FSSetPermission command =
541              new FSOperations.FSSetPermission(path, permission);
542            fsExecute(user, doAs, command);
543            AUDIT_LOG.info("[{}] to [{}]", path, permission);
544            response = Response.ok().build();
545            break;
546          }
547          case SETREPLICATION: {
548            Short replication = params.get(ReplicationParam.NAME,
549                                           ReplicationParam.class);
550            FSOperations.FSSetReplication command =
551              new FSOperations.FSSetReplication(path, replication);
552            JSONObject json = fsExecute(user, doAs, command);
553            AUDIT_LOG.info("[{}] to [{}]", path, replication);
554            response = Response.ok(json).build();
555            break;
556          }
557          case SETTIMES: {
558            Long modifiedTime = params.get(ModifiedTimeParam.NAME,
559                                           ModifiedTimeParam.class);
560            Long accessTime = params.get(AccessTimeParam.NAME,
561                                         AccessTimeParam.class);
562            FSOperations.FSSetTimes command =
563              new FSOperations.FSSetTimes(path, modifiedTime, accessTime);
564            fsExecute(user, doAs, command);
565            AUDIT_LOG.info("[{}] to (M/A)[{}]", path,
566                           modifiedTime + ":" + accessTime);
567            response = Response.ok().build();
568            break;
569          }
570          default: {
571            throw new IOException(
572              MessageFormat.format("Invalid HTTP PUT operation [{0}]",
573                                   op.value()));
574          }
575        }
576        return response;
577      }
578    
579    }