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 package org.apache.hadoop.fs.http.client;
019
020 import java.util.ArrayList;
021 import java.util.EnumSet;
022 import java.util.List;
023 import org.apache.hadoop.classification.InterfaceAudience;
024 import org.apache.hadoop.conf.Configuration;
025 import org.apache.hadoop.fs.ContentSummary;
026 import org.apache.hadoop.fs.DelegationTokenRenewer;
027 import org.apache.hadoop.fs.FSDataInputStream;
028 import org.apache.hadoop.fs.FSDataOutputStream;
029 import org.apache.hadoop.fs.FileChecksum;
030 import org.apache.hadoop.fs.FileStatus;
031 import org.apache.hadoop.fs.FileSystem;
032 import org.apache.hadoop.fs.Path;
033 import org.apache.hadoop.fs.PositionedReadable;
034 import org.apache.hadoop.fs.Seekable;
035 import org.apache.hadoop.fs.XAttrCodec;
036 import org.apache.hadoop.fs.XAttrSetFlag;
037 import org.apache.hadoop.fs.permission.AclEntry;
038 import org.apache.hadoop.fs.permission.AclStatus;
039 import org.apache.hadoop.fs.permission.FsPermission;
040 import org.apache.hadoop.hdfs.DFSConfigKeys;
041 import org.apache.hadoop.lib.wsrs.EnumSetParam;
042 import org.apache.hadoop.security.UserGroupInformation;
043 import org.apache.hadoop.security.token.Token;
044 import org.apache.hadoop.security.token.TokenIdentifier;
045 import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticatedURL;
046 import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticator;
047 import org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator;
048 import org.apache.hadoop.util.HttpExceptionUtils;
049 import org.apache.hadoop.util.Progressable;
050 import org.apache.hadoop.util.ReflectionUtils;
051 import org.apache.hadoop.util.StringUtils;
052 import org.json.simple.JSONArray;
053 import org.json.simple.JSONObject;
054 import org.json.simple.parser.JSONParser;
055 import org.json.simple.parser.ParseException;
056
057 import com.google.common.base.Preconditions;
058 import com.google.common.collect.Lists;
059 import com.google.common.collect.Maps;
060
061 import java.io.BufferedInputStream;
062 import java.io.BufferedOutputStream;
063 import java.io.DataInput;
064 import java.io.DataOutput;
065 import java.io.FileNotFoundException;
066 import java.io.FilterInputStream;
067 import java.io.IOException;
068 import java.io.InputStream;
069 import java.io.OutputStream;
070 import java.net.HttpURLConnection;
071 import java.net.URI;
072 import java.net.URISyntaxException;
073 import java.net.URL;
074 import java.security.PrivilegedExceptionAction;
075 import java.text.MessageFormat;
076 import java.util.HashMap;
077 import java.util.Map;
078
079 /**
080 * HttpFSServer implementation of the FileSystemAccess FileSystem.
081 * <p/>
082 * This implementation allows a user to access HDFS over HTTP via a HttpFSServer server.
083 */
084 @InterfaceAudience.Private
085 public class HttpFSFileSystem extends FileSystem
086 implements DelegationTokenRenewer.Renewable {
087
088 public static final String SERVICE_NAME = HttpFSUtils.SERVICE_NAME;
089
090 public static final String SERVICE_VERSION = HttpFSUtils.SERVICE_VERSION;
091
092 public static final String SCHEME = "webhdfs";
093
094 public static final String OP_PARAM = "op";
095 public static final String DO_AS_PARAM = "doas";
096 public static final String OVERWRITE_PARAM = "overwrite";
097 public static final String REPLICATION_PARAM = "replication";
098 public static final String BLOCKSIZE_PARAM = "blocksize";
099 public static final String PERMISSION_PARAM = "permission";
100 public static final String ACLSPEC_PARAM = "aclspec";
101 public static final String DESTINATION_PARAM = "destination";
102 public static final String RECURSIVE_PARAM = "recursive";
103 public static final String SOURCES_PARAM = "sources";
104 public static final String OWNER_PARAM = "owner";
105 public static final String GROUP_PARAM = "group";
106 public static final String MODIFICATION_TIME_PARAM = "modificationtime";
107 public static final String ACCESS_TIME_PARAM = "accesstime";
108 public static final String XATTR_NAME_PARAM = "xattr.name";
109 public static final String XATTR_VALUE_PARAM = "xattr.value";
110 public static final String XATTR_SET_FLAG_PARAM = "flag";
111 public static final String XATTR_ENCODING_PARAM = "encoding";
112
113 public static final Short DEFAULT_PERMISSION = 0755;
114 public static final String ACLSPEC_DEFAULT = "";
115
116 public static final String RENAME_JSON = "boolean";
117
118 public static final String DELETE_JSON = "boolean";
119
120 public static final String MKDIRS_JSON = "boolean";
121
122 public static final String HOME_DIR_JSON = "Path";
123
124 public static final String SET_REPLICATION_JSON = "boolean";
125
126 public static final String UPLOAD_CONTENT_TYPE= "application/octet-stream";
127
128 public static enum FILE_TYPE {
129 FILE, DIRECTORY, SYMLINK;
130
131 public static FILE_TYPE getType(FileStatus fileStatus) {
132 if (fileStatus.isFile()) {
133 return FILE;
134 }
135 if (fileStatus.isDirectory()) {
136 return DIRECTORY;
137 }
138 if (fileStatus.isSymlink()) {
139 return SYMLINK;
140 }
141 throw new IllegalArgumentException("Could not determine filetype for: " +
142 fileStatus.getPath());
143 }
144 }
145
146 public static final String FILE_STATUSES_JSON = "FileStatuses";
147 public static final String FILE_STATUS_JSON = "FileStatus";
148 public static final String PATH_SUFFIX_JSON = "pathSuffix";
149 public static final String TYPE_JSON = "type";
150 public static final String LENGTH_JSON = "length";
151 public static final String OWNER_JSON = "owner";
152 public static final String GROUP_JSON = "group";
153 public static final String PERMISSION_JSON = "permission";
154 public static final String ACCESS_TIME_JSON = "accessTime";
155 public static final String MODIFICATION_TIME_JSON = "modificationTime";
156 public static final String BLOCK_SIZE_JSON = "blockSize";
157 public static final String REPLICATION_JSON = "replication";
158 public static final String XATTRS_JSON = "XAttrs";
159 public static final String XATTR_NAME_JSON = "name";
160 public static final String XATTR_VALUE_JSON = "value";
161 public static final String XATTRNAMES_JSON = "XAttrNames";
162
163 public static final String FILE_CHECKSUM_JSON = "FileChecksum";
164 public static final String CHECKSUM_ALGORITHM_JSON = "algorithm";
165 public static final String CHECKSUM_BYTES_JSON = "bytes";
166 public static final String CHECKSUM_LENGTH_JSON = "length";
167
168 public static final String CONTENT_SUMMARY_JSON = "ContentSummary";
169 public static final String CONTENT_SUMMARY_DIRECTORY_COUNT_JSON = "directoryCount";
170 public static final String CONTENT_SUMMARY_FILE_COUNT_JSON = "fileCount";
171 public static final String CONTENT_SUMMARY_LENGTH_JSON = "length";
172 public static final String CONTENT_SUMMARY_QUOTA_JSON = "quota";
173 public static final String CONTENT_SUMMARY_SPACE_CONSUMED_JSON = "spaceConsumed";
174 public static final String CONTENT_SUMMARY_SPACE_QUOTA_JSON = "spaceQuota";
175
176 public static final String ACL_STATUS_JSON = "AclStatus";
177 public static final String ACL_STICKY_BIT_JSON = "stickyBit";
178 public static final String ACL_ENTRIES_JSON = "entries";
179 public static final String ACL_BIT_JSON = "aclBit";
180
181 public static final int HTTP_TEMPORARY_REDIRECT = 307;
182
183 private static final String HTTP_GET = "GET";
184 private static final String HTTP_PUT = "PUT";
185 private static final String HTTP_POST = "POST";
186 private static final String HTTP_DELETE = "DELETE";
187
188 @InterfaceAudience.Private
189 public static enum Operation {
190 OPEN(HTTP_GET), GETFILESTATUS(HTTP_GET), LISTSTATUS(HTTP_GET),
191 GETHOMEDIRECTORY(HTTP_GET), GETCONTENTSUMMARY(HTTP_GET),
192 GETFILECHECKSUM(HTTP_GET), GETFILEBLOCKLOCATIONS(HTTP_GET),
193 INSTRUMENTATION(HTTP_GET), GETACLSTATUS(HTTP_GET),
194 APPEND(HTTP_POST), CONCAT(HTTP_POST),
195 CREATE(HTTP_PUT), MKDIRS(HTTP_PUT), RENAME(HTTP_PUT), SETOWNER(HTTP_PUT),
196 SETPERMISSION(HTTP_PUT), SETREPLICATION(HTTP_PUT), SETTIMES(HTTP_PUT),
197 MODIFYACLENTRIES(HTTP_PUT), REMOVEACLENTRIES(HTTP_PUT),
198 REMOVEDEFAULTACL(HTTP_PUT), REMOVEACL(HTTP_PUT), SETACL(HTTP_PUT),
199 DELETE(HTTP_DELETE), SETXATTR(HTTP_PUT), GETXATTRS(HTTP_GET),
200 REMOVEXATTR(HTTP_PUT), LISTXATTRS(HTTP_GET);
201
202 private String httpMethod;
203
204 Operation(String httpMethod) {
205 this.httpMethod = httpMethod;
206 }
207
208 public String getMethod() {
209 return httpMethod;
210 }
211
212 }
213
214 private DelegationTokenAuthenticatedURL authURL;
215 private DelegationTokenAuthenticatedURL.Token authToken =
216 new DelegationTokenAuthenticatedURL.Token();
217 private URI uri;
218 private Path workingDir;
219 private UserGroupInformation realUser;
220
221
222
223 /**
224 * Convenience method that creates a <code>HttpURLConnection</code> for the
225 * HttpFSServer file system operations.
226 * <p/>
227 * This methods performs and injects any needed authentication credentials
228 * via the {@link #getConnection(URL, String)} method
229 *
230 * @param method the HTTP method.
231 * @param params the query string parameters.
232 * @param path the file path
233 * @param makeQualified if the path should be 'makeQualified'
234 *
235 * @return a <code>HttpURLConnection</code> for the HttpFSServer server,
236 * authenticated and ready to use for the specified path and file system operation.
237 *
238 * @throws IOException thrown if an IO error occurrs.
239 */
240 private HttpURLConnection getConnection(final String method,
241 Map<String, String> params, Path path, boolean makeQualified)
242 throws IOException {
243 return getConnection(method, params, null, path, makeQualified);
244 }
245
246 /**
247 * Convenience method that creates a <code>HttpURLConnection</code> for the
248 * HttpFSServer file system operations.
249 * <p/>
250 * This methods performs and injects any needed authentication credentials
251 * via the {@link #getConnection(URL, String)} method
252 *
253 * @param method the HTTP method.
254 * @param params the query string parameters.
255 * @param multiValuedParams multi valued parameters of the query string
256 * @param path the file path
257 * @param makeQualified if the path should be 'makeQualified'
258 *
259 * @return HttpURLConnection a <code>HttpURLConnection</code> for the
260 * HttpFSServer server, authenticated and ready to use for the
261 * specified path and file system operation.
262 *
263 * @throws IOException thrown if an IO error occurrs.
264 */
265 private HttpURLConnection getConnection(final String method,
266 Map<String, String> params, Map<String, List<String>> multiValuedParams,
267 Path path, boolean makeQualified) throws IOException {
268 if (makeQualified) {
269 path = makeQualified(path);
270 }
271 final URL url = HttpFSUtils.createURL(path, params, multiValuedParams);
272 try {
273 return UserGroupInformation.getCurrentUser().doAs(
274 new PrivilegedExceptionAction<HttpURLConnection>() {
275 @Override
276 public HttpURLConnection run() throws Exception {
277 return getConnection(url, method);
278 }
279 }
280 );
281 } catch (Exception ex) {
282 if (ex instanceof IOException) {
283 throw (IOException) ex;
284 } else {
285 throw new IOException(ex);
286 }
287 }
288 }
289
290 /**
291 * Convenience method that creates a <code>HttpURLConnection</code> for the specified URL.
292 * <p/>
293 * This methods performs and injects any needed authentication credentials.
294 *
295 * @param url url to connect to.
296 * @param method the HTTP method.
297 *
298 * @return a <code>HttpURLConnection</code> for the HttpFSServer server, authenticated and ready to use for
299 * the specified path and file system operation.
300 *
301 * @throws IOException thrown if an IO error occurrs.
302 */
303 private HttpURLConnection getConnection(URL url, String method) throws IOException {
304 try {
305 HttpURLConnection conn = authURL.openConnection(url, authToken);
306 conn.setRequestMethod(method);
307 if (method.equals(HTTP_POST) || method.equals(HTTP_PUT)) {
308 conn.setDoOutput(true);
309 }
310 return conn;
311 } catch (Exception ex) {
312 throw new IOException(ex);
313 }
314 }
315
316 /**
317 * Called after a new FileSystem instance is constructed.
318 *
319 * @param name a uri whose authority section names the host, port, etc. for this FileSystem
320 * @param conf the configuration
321 */
322 @Override
323 public void initialize(URI name, Configuration conf) throws IOException {
324 UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
325
326 //the real use is the one that has the Kerberos credentials needed for
327 //SPNEGO to work
328 realUser = ugi.getRealUser();
329 if (realUser == null) {
330 realUser = UserGroupInformation.getLoginUser();
331 }
332 super.initialize(name, conf);
333 try {
334 uri = new URI(name.getScheme() + "://" + name.getAuthority());
335 } catch (URISyntaxException ex) {
336 throw new IOException(ex);
337 }
338
339 Class<? extends DelegationTokenAuthenticator> klass =
340 getConf().getClass("httpfs.authenticator.class",
341 KerberosDelegationTokenAuthenticator.class,
342 DelegationTokenAuthenticator.class);
343 DelegationTokenAuthenticator authenticator =
344 ReflectionUtils.newInstance(klass, getConf());
345 authURL = new DelegationTokenAuthenticatedURL(authenticator);
346 }
347
348 @Override
349 public String getScheme() {
350 return SCHEME;
351 }
352
353 /**
354 * Returns a URI whose scheme and authority identify this FileSystem.
355 *
356 * @return the URI whose scheme and authority identify this FileSystem.
357 */
358 @Override
359 public URI getUri() {
360 return uri;
361 }
362
363 /**
364 * Get the default port for this file system.
365 * @return the default port or 0 if there isn't one
366 */
367 @Override
368 protected int getDefaultPort() {
369 return getConf().getInt(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_KEY,
370 DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_DEFAULT);
371 }
372
373 /**
374 * HttpFSServer subclass of the <code>FSDataInputStream</code>.
375 * <p/>
376 * This implementation does not support the
377 * <code>PositionReadable</code> and <code>Seekable</code> methods.
378 */
379 private static class HttpFSDataInputStream extends FilterInputStream implements Seekable, PositionedReadable {
380
381 protected HttpFSDataInputStream(InputStream in, int bufferSize) {
382 super(new BufferedInputStream(in, bufferSize));
383 }
384
385 @Override
386 public int read(long position, byte[] buffer, int offset, int length) throws IOException {
387 throw new UnsupportedOperationException();
388 }
389
390 @Override
391 public void readFully(long position, byte[] buffer, int offset, int length) throws IOException {
392 throw new UnsupportedOperationException();
393 }
394
395 @Override
396 public void readFully(long position, byte[] buffer) throws IOException {
397 throw new UnsupportedOperationException();
398 }
399
400 @Override
401 public void seek(long pos) throws IOException {
402 throw new UnsupportedOperationException();
403 }
404
405 @Override
406 public long getPos() throws IOException {
407 throw new UnsupportedOperationException();
408 }
409
410 @Override
411 public boolean seekToNewSource(long targetPos) throws IOException {
412 throw new UnsupportedOperationException();
413 }
414 }
415
416 /**
417 * Opens an FSDataInputStream at the indicated Path.
418 * </p>
419 * IMPORTANT: the returned <code><FSDataInputStream/code> does not support the
420 * <code>PositionReadable</code> and <code>Seekable</code> methods.
421 *
422 * @param f the file name to open
423 * @param bufferSize the size of the buffer to be used.
424 */
425 @Override
426 public FSDataInputStream open(Path f, int bufferSize) throws IOException {
427 Map<String, String> params = new HashMap<String, String>();
428 params.put(OP_PARAM, Operation.OPEN.toString());
429 HttpURLConnection conn = getConnection(Operation.OPEN.getMethod(), params,
430 f, true);
431 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
432 return new FSDataInputStream(
433 new HttpFSDataInputStream(conn.getInputStream(), bufferSize));
434 }
435
436 /**
437 * HttpFSServer subclass of the <code>FSDataOutputStream</code>.
438 * <p/>
439 * This implementation closes the underlying HTTP connection validating the Http connection status
440 * at closing time.
441 */
442 private static class HttpFSDataOutputStream extends FSDataOutputStream {
443 private HttpURLConnection conn;
444 private int closeStatus;
445
446 public HttpFSDataOutputStream(HttpURLConnection conn, OutputStream out, int closeStatus, Statistics stats)
447 throws IOException {
448 super(out, stats);
449 this.conn = conn;
450 this.closeStatus = closeStatus;
451 }
452
453 @Override
454 public void close() throws IOException {
455 try {
456 super.close();
457 } finally {
458 HttpExceptionUtils.validateResponse(conn, closeStatus);
459 }
460 }
461
462 }
463
464 /**
465 * Converts a <code>FsPermission</code> to a Unix octal representation.
466 *
467 * @param p the permission.
468 *
469 * @return the Unix string symbolic reprentation.
470 */
471 public static String permissionToString(FsPermission p) {
472 return Integer.toString((p == null) ? DEFAULT_PERMISSION : p.toShort(), 8);
473 }
474
475 /*
476 * Common handling for uploading data for create and append operations.
477 */
478 private FSDataOutputStream uploadData(String method, Path f, Map<String, String> params,
479 int bufferSize, int expectedStatus) throws IOException {
480 HttpURLConnection conn = getConnection(method, params, f, true);
481 conn.setInstanceFollowRedirects(false);
482 boolean exceptionAlreadyHandled = false;
483 try {
484 if (conn.getResponseCode() == HTTP_TEMPORARY_REDIRECT) {
485 exceptionAlreadyHandled = true;
486 String location = conn.getHeaderField("Location");
487 if (location != null) {
488 conn = getConnection(new URL(location), method);
489 conn.setRequestProperty("Content-Type", UPLOAD_CONTENT_TYPE);
490 try {
491 OutputStream os = new BufferedOutputStream(conn.getOutputStream(), bufferSize);
492 return new HttpFSDataOutputStream(conn, os, expectedStatus, statistics);
493 } catch (IOException ex) {
494 HttpExceptionUtils.validateResponse(conn, expectedStatus);
495 throw ex;
496 }
497 } else {
498 HttpExceptionUtils.validateResponse(conn, HTTP_TEMPORARY_REDIRECT);
499 throw new IOException("Missing HTTP 'Location' header for [" + conn.getURL() + "]");
500 }
501 } else {
502 throw new IOException(
503 MessageFormat.format("Expected HTTP status was [307], received [{0}]",
504 conn.getResponseCode()));
505 }
506 } catch (IOException ex) {
507 if (exceptionAlreadyHandled) {
508 throw ex;
509 } else {
510 HttpExceptionUtils.validateResponse(conn, HTTP_TEMPORARY_REDIRECT);
511 throw ex;
512 }
513 }
514 }
515
516
517 /**
518 * Opens an FSDataOutputStream at the indicated Path with write-progress
519 * reporting.
520 * <p/>
521 * IMPORTANT: The <code>Progressable</code> parameter is not used.
522 *
523 * @param f the file name to open.
524 * @param permission file permission.
525 * @param overwrite if a file with this name already exists, then if true,
526 * the file will be overwritten, and if false an error will be thrown.
527 * @param bufferSize the size of the buffer to be used.
528 * @param replication required block replication for the file.
529 * @param blockSize block size.
530 * @param progress progressable.
531 *
532 * @throws IOException
533 * @see #setPermission(Path, FsPermission)
534 */
535 @Override
536 public FSDataOutputStream create(Path f, FsPermission permission,
537 boolean overwrite, int bufferSize,
538 short replication, long blockSize,
539 Progressable progress) throws IOException {
540 Map<String, String> params = new HashMap<String, String>();
541 params.put(OP_PARAM, Operation.CREATE.toString());
542 params.put(OVERWRITE_PARAM, Boolean.toString(overwrite));
543 params.put(REPLICATION_PARAM, Short.toString(replication));
544 params.put(BLOCKSIZE_PARAM, Long.toString(blockSize));
545 params.put(PERMISSION_PARAM, permissionToString(permission));
546 return uploadData(Operation.CREATE.getMethod(), f, params, bufferSize,
547 HttpURLConnection.HTTP_CREATED);
548 }
549
550
551 /**
552 * Append to an existing file (optional operation).
553 * <p/>
554 * IMPORTANT: The <code>Progressable</code> parameter is not used.
555 *
556 * @param f the existing file to be appended.
557 * @param bufferSize the size of the buffer to be used.
558 * @param progress for reporting progress if it is not null.
559 *
560 * @throws IOException
561 */
562 @Override
563 public FSDataOutputStream append(Path f, int bufferSize,
564 Progressable progress) throws IOException {
565 Map<String, String> params = new HashMap<String, String>();
566 params.put(OP_PARAM, Operation.APPEND.toString());
567 return uploadData(Operation.APPEND.getMethod(), f, params, bufferSize,
568 HttpURLConnection.HTTP_OK);
569 }
570
571 /**
572 * Concat existing files together.
573 * @param f the path to the target destination.
574 * @param psrcs the paths to the sources to use for the concatenation.
575 *
576 * @throws IOException
577 */
578 @Override
579 public void concat(Path f, Path[] psrcs) throws IOException {
580 List<String> strPaths = new ArrayList<String>(psrcs.length);
581 for(Path psrc : psrcs) {
582 strPaths.add(psrc.toUri().getPath());
583 }
584 String srcs = StringUtils.join(",", strPaths);
585
586 Map<String, String> params = new HashMap<String, String>();
587 params.put(OP_PARAM, Operation.CONCAT.toString());
588 params.put(SOURCES_PARAM, srcs);
589 HttpURLConnection conn = getConnection(Operation.CONCAT.getMethod(),
590 params, f, true);
591 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
592 }
593
594 /**
595 * Renames Path src to Path dst. Can take place on local fs
596 * or remote DFS.
597 */
598 @Override
599 public boolean rename(Path src, Path dst) throws IOException {
600 Map<String, String> params = new HashMap<String, String>();
601 params.put(OP_PARAM, Operation.RENAME.toString());
602 params.put(DESTINATION_PARAM, dst.toString());
603 HttpURLConnection conn = getConnection(Operation.RENAME.getMethod(),
604 params, src, true);
605 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
606 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
607 return (Boolean) json.get(RENAME_JSON);
608 }
609
610 /**
611 * Delete a file.
612 *
613 * @deprecated Use delete(Path, boolean) instead
614 */
615 @Deprecated
616 @Override
617 public boolean delete(Path f) throws IOException {
618 return delete(f, false);
619 }
620
621 /**
622 * Delete a file.
623 *
624 * @param f the path to delete.
625 * @param recursive if path is a directory and set to
626 * true, the directory is deleted else throws an exception. In
627 * case of a file the recursive can be set to either true or false.
628 *
629 * @return true if delete is successful else false.
630 *
631 * @throws IOException
632 */
633 @Override
634 public boolean delete(Path f, boolean recursive) throws IOException {
635 Map<String, String> params = new HashMap<String, String>();
636 params.put(OP_PARAM, Operation.DELETE.toString());
637 params.put(RECURSIVE_PARAM, Boolean.toString(recursive));
638 HttpURLConnection conn = getConnection(Operation.DELETE.getMethod(),
639 params, f, true);
640 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
641 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
642 return (Boolean) json.get(DELETE_JSON);
643 }
644
645 /**
646 * List the statuses of the files/directories in the given path if the path is
647 * a directory.
648 *
649 * @param f given path
650 *
651 * @return the statuses of the files/directories in the given patch
652 *
653 * @throws IOException
654 */
655 @Override
656 public FileStatus[] listStatus(Path f) throws IOException {
657 Map<String, String> params = new HashMap<String, String>();
658 params.put(OP_PARAM, Operation.LISTSTATUS.toString());
659 HttpURLConnection conn = getConnection(Operation.LISTSTATUS.getMethod(),
660 params, f, true);
661 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
662 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
663 json = (JSONObject) json.get(FILE_STATUSES_JSON);
664 JSONArray jsonArray = (JSONArray) json.get(FILE_STATUS_JSON);
665 FileStatus[] array = new FileStatus[jsonArray.size()];
666 f = makeQualified(f);
667 for (int i = 0; i < jsonArray.size(); i++) {
668 array[i] = createFileStatus(f, (JSONObject) jsonArray.get(i));
669 }
670 return array;
671 }
672
673 /**
674 * Set the current working directory for the given file system. All relative
675 * paths will be resolved relative to it.
676 *
677 * @param newDir new directory.
678 */
679 @Override
680 public void setWorkingDirectory(Path newDir) {
681 workingDir = newDir;
682 }
683
684 /**
685 * Get the current working directory for the given file system
686 *
687 * @return the directory pathname
688 */
689 @Override
690 public Path getWorkingDirectory() {
691 if (workingDir == null) {
692 workingDir = getHomeDirectory();
693 }
694 return workingDir;
695 }
696
697 /**
698 * Make the given file and all non-existent parents into
699 * directories. Has the semantics of Unix 'mkdir -p'.
700 * Existence of the directory hierarchy is not an error.
701 */
702 @Override
703 public boolean mkdirs(Path f, FsPermission permission) throws IOException {
704 Map<String, String> params = new HashMap<String, String>();
705 params.put(OP_PARAM, Operation.MKDIRS.toString());
706 params.put(PERMISSION_PARAM, permissionToString(permission));
707 HttpURLConnection conn = getConnection(Operation.MKDIRS.getMethod(),
708 params, f, true);
709 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
710 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
711 return (Boolean) json.get(MKDIRS_JSON);
712 }
713
714 /**
715 * Return a file status object that represents the path.
716 *
717 * @param f The path we want information from
718 *
719 * @return a FileStatus object
720 *
721 * @throws FileNotFoundException when the path does not exist;
722 * IOException see specific implementation
723 */
724 @Override
725 public FileStatus getFileStatus(Path f) throws IOException {
726 Map<String, String> params = new HashMap<String, String>();
727 params.put(OP_PARAM, Operation.GETFILESTATUS.toString());
728 HttpURLConnection conn = getConnection(Operation.GETFILESTATUS.getMethod(),
729 params, f, true);
730 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
731 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
732 json = (JSONObject) json.get(FILE_STATUS_JSON);
733 f = makeQualified(f);
734 return createFileStatus(f, json);
735 }
736
737 /**
738 * Return the current user's home directory in this filesystem.
739 * The default implementation returns "/user/$USER/".
740 */
741 @Override
742 public Path getHomeDirectory() {
743 Map<String, String> params = new HashMap<String, String>();
744 params.put(OP_PARAM, Operation.GETHOMEDIRECTORY.toString());
745 try {
746 HttpURLConnection conn =
747 getConnection(Operation.GETHOMEDIRECTORY.getMethod(), params,
748 new Path(getUri().toString(), "/"), false);
749 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
750 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
751 return new Path((String) json.get(HOME_DIR_JSON));
752 } catch (IOException ex) {
753 throw new RuntimeException(ex);
754 }
755 }
756
757 /**
758 * Set owner of a path (i.e. a file or a directory).
759 * The parameters username and groupname cannot both be null.
760 *
761 * @param p The path
762 * @param username If it is null, the original username remains unchanged.
763 * @param groupname If it is null, the original groupname remains unchanged.
764 */
765 @Override
766 public void setOwner(Path p, String username, String groupname)
767 throws IOException {
768 Map<String, String> params = new HashMap<String, String>();
769 params.put(OP_PARAM, Operation.SETOWNER.toString());
770 params.put(OWNER_PARAM, username);
771 params.put(GROUP_PARAM, groupname);
772 HttpURLConnection conn = getConnection(Operation.SETOWNER.getMethod(),
773 params, p, true);
774 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
775 }
776
777 /**
778 * Set permission of a path.
779 *
780 * @param p path.
781 * @param permission permission.
782 */
783 @Override
784 public void setPermission(Path p, FsPermission permission) throws IOException {
785 Map<String, String> params = new HashMap<String, String>();
786 params.put(OP_PARAM, Operation.SETPERMISSION.toString());
787 params.put(PERMISSION_PARAM, permissionToString(permission));
788 HttpURLConnection conn = getConnection(Operation.SETPERMISSION.getMethod(), params, p, true);
789 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
790 }
791
792 /**
793 * Set access time of a file
794 *
795 * @param p The path
796 * @param mtime Set the modification time of this file.
797 * The number of milliseconds since Jan 1, 1970.
798 * A value of -1 means that this call should not set modification time.
799 * @param atime Set the access time of this file.
800 * The number of milliseconds since Jan 1, 1970.
801 * A value of -1 means that this call should not set access time.
802 */
803 @Override
804 public void setTimes(Path p, long mtime, long atime) throws IOException {
805 Map<String, String> params = new HashMap<String, String>();
806 params.put(OP_PARAM, Operation.SETTIMES.toString());
807 params.put(MODIFICATION_TIME_PARAM, Long.toString(mtime));
808 params.put(ACCESS_TIME_PARAM, Long.toString(atime));
809 HttpURLConnection conn = getConnection(Operation.SETTIMES.getMethod(),
810 params, p, true);
811 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
812 }
813
814 /**
815 * Set replication for an existing file.
816 *
817 * @param src file name
818 * @param replication new replication
819 *
820 * @return true if successful;
821 * false if file does not exist or is a directory
822 *
823 * @throws IOException
824 */
825 @Override
826 public boolean setReplication(Path src, short replication)
827 throws IOException {
828 Map<String, String> params = new HashMap<String, String>();
829 params.put(OP_PARAM, Operation.SETREPLICATION.toString());
830 params.put(REPLICATION_PARAM, Short.toString(replication));
831 HttpURLConnection conn =
832 getConnection(Operation.SETREPLICATION.getMethod(), params, src, true);
833 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
834 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
835 return (Boolean) json.get(SET_REPLICATION_JSON);
836 }
837
838 /**
839 * Modify the ACL entries for a file.
840 *
841 * @param path Path to modify
842 * @param aclSpec List<AclEntry> describing modifications
843 * @throws IOException
844 */
845 @Override
846 public void modifyAclEntries(Path path, List<AclEntry> aclSpec)
847 throws IOException {
848 Map<String, String> params = new HashMap<String, String>();
849 params.put(OP_PARAM, Operation.MODIFYACLENTRIES.toString());
850 params.put(ACLSPEC_PARAM, AclEntry.aclSpecToString(aclSpec));
851 HttpURLConnection conn = getConnection(
852 Operation.MODIFYACLENTRIES.getMethod(), params, path, true);
853 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
854 }
855
856 /**
857 * Remove the specified ACL entries from a file
858 * @param path Path to modify
859 * @param aclSpec List<AclEntry> describing entries to remove
860 * @throws IOException
861 */
862 @Override
863 public void removeAclEntries(Path path, List<AclEntry> aclSpec)
864 throws IOException {
865 Map<String, String> params = new HashMap<String, String>();
866 params.put(OP_PARAM, Operation.REMOVEACLENTRIES.toString());
867 params.put(ACLSPEC_PARAM, AclEntry.aclSpecToString(aclSpec));
868 HttpURLConnection conn = getConnection(
869 Operation.REMOVEACLENTRIES.getMethod(), params, path, true);
870 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
871 }
872
873 /**
874 * Removes the default ACL for the given file
875 * @param path Path from which to remove the default ACL.
876 * @throws IOException
877 */
878 @Override
879 public void removeDefaultAcl(Path path) throws IOException {
880 Map<String, String> params = new HashMap<String, String>();
881 params.put(OP_PARAM, Operation.REMOVEDEFAULTACL.toString());
882 HttpURLConnection conn = getConnection(
883 Operation.REMOVEDEFAULTACL.getMethod(), params, path, true);
884 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
885 }
886
887 /**
888 * Remove all ACLs from a file
889 * @param path Path from which to remove all ACLs
890 * @throws IOException
891 */
892 @Override
893 public void removeAcl(Path path) throws IOException {
894 Map<String, String> params = new HashMap<String, String>();
895 params.put(OP_PARAM, Operation.REMOVEACL.toString());
896 HttpURLConnection conn = getConnection(Operation.REMOVEACL.getMethod(),
897 params, path, true);
898 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
899 }
900
901 /**
902 * Set the ACLs for the given file
903 * @param path Path to modify
904 * @param aclSpec List<AclEntry> describing modifications, must include
905 * entries for user, group, and others for compatibility
906 * with permission bits.
907 * @throws IOException
908 */
909 @Override
910 public void setAcl(Path path, List<AclEntry> aclSpec) throws IOException {
911 Map<String, String> params = new HashMap<String, String>();
912 params.put(OP_PARAM, Operation.SETACL.toString());
913 params.put(ACLSPEC_PARAM, AclEntry.aclSpecToString(aclSpec));
914 HttpURLConnection conn = getConnection(Operation.SETACL.getMethod(),
915 params, path, true);
916 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
917 }
918
919 /**
920 * Get the ACL information for a given file
921 * @param path Path to acquire ACL info for
922 * @return the ACL information in JSON format
923 * @throws IOException
924 */
925 @Override
926 public AclStatus getAclStatus(Path path) throws IOException {
927 Map<String, String> params = new HashMap<String, String>();
928 params.put(OP_PARAM, Operation.GETACLSTATUS.toString());
929 HttpURLConnection conn = getConnection(Operation.GETACLSTATUS.getMethod(),
930 params, path, true);
931 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
932 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
933 json = (JSONObject) json.get(ACL_STATUS_JSON);
934 return createAclStatus(json);
935 }
936
937 private FileStatus createFileStatus(Path parent, JSONObject json) {
938 String pathSuffix = (String) json.get(PATH_SUFFIX_JSON);
939 Path path = (pathSuffix.equals("")) ? parent : new Path(parent, pathSuffix);
940 FILE_TYPE type = FILE_TYPE.valueOf((String) json.get(TYPE_JSON));
941 long len = (Long) json.get(LENGTH_JSON);
942 String owner = (String) json.get(OWNER_JSON);
943 String group = (String) json.get(GROUP_JSON);
944 FsPermission permission =
945 new FsPermission(Short.parseShort((String) json.get(PERMISSION_JSON), 8));
946 long aTime = (Long) json.get(ACCESS_TIME_JSON);
947 long mTime = (Long) json.get(MODIFICATION_TIME_JSON);
948 long blockSize = (Long) json.get(BLOCK_SIZE_JSON);
949 short replication = ((Long) json.get(REPLICATION_JSON)).shortValue();
950 FileStatus fileStatus = null;
951
952 switch (type) {
953 case FILE:
954 case DIRECTORY:
955 fileStatus = new FileStatus(len, (type == FILE_TYPE.DIRECTORY),
956 replication, blockSize, mTime, aTime,
957 permission, owner, group, path);
958 break;
959 case SYMLINK:
960 Path symLink = null;
961 fileStatus = new FileStatus(len, false,
962 replication, blockSize, mTime, aTime,
963 permission, owner, group, symLink,
964 path);
965 }
966 return fileStatus;
967 }
968
969 /**
970 * Convert the given JSON object into an AclStatus
971 * @param json Input JSON representing the ACLs
972 * @return Resulting AclStatus
973 */
974 private AclStatus createAclStatus(JSONObject json) {
975 AclStatus.Builder aclStatusBuilder = new AclStatus.Builder()
976 .owner((String) json.get(OWNER_JSON))
977 .group((String) json.get(GROUP_JSON))
978 .stickyBit((Boolean) json.get(ACL_STICKY_BIT_JSON));
979 JSONArray entries = (JSONArray) json.get(ACL_ENTRIES_JSON);
980 for ( Object e : entries ) {
981 aclStatusBuilder.addEntry(AclEntry.parseAclEntry(e.toString(), true));
982 }
983 return aclStatusBuilder.build();
984 }
985
986 @Override
987 public ContentSummary getContentSummary(Path f) throws IOException {
988 Map<String, String> params = new HashMap<String, String>();
989 params.put(OP_PARAM, Operation.GETCONTENTSUMMARY.toString());
990 HttpURLConnection conn =
991 getConnection(Operation.GETCONTENTSUMMARY.getMethod(), params, f, true);
992 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
993 JSONObject json = (JSONObject) ((JSONObject)
994 HttpFSUtils.jsonParse(conn)).get(CONTENT_SUMMARY_JSON);
995 return new ContentSummary((Long) json.get(CONTENT_SUMMARY_LENGTH_JSON),
996 (Long) json.get(CONTENT_SUMMARY_FILE_COUNT_JSON),
997 (Long) json.get(CONTENT_SUMMARY_DIRECTORY_COUNT_JSON),
998 (Long) json.get(CONTENT_SUMMARY_QUOTA_JSON),
999 (Long) json.get(CONTENT_SUMMARY_SPACE_CONSUMED_JSON),
1000 (Long) json.get(CONTENT_SUMMARY_SPACE_QUOTA_JSON)
1001 );
1002 }
1003
1004 @Override
1005 public FileChecksum getFileChecksum(Path f) throws IOException {
1006 Map<String, String> params = new HashMap<String, String>();
1007 params.put(OP_PARAM, Operation.GETFILECHECKSUM.toString());
1008 HttpURLConnection conn =
1009 getConnection(Operation.GETFILECHECKSUM.getMethod(), params, f, true);
1010 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1011 final JSONObject json = (JSONObject) ((JSONObject)
1012 HttpFSUtils.jsonParse(conn)).get(FILE_CHECKSUM_JSON);
1013 return new FileChecksum() {
1014 @Override
1015 public String getAlgorithmName() {
1016 return (String) json.get(CHECKSUM_ALGORITHM_JSON);
1017 }
1018
1019 @Override
1020 public int getLength() {
1021 return ((Long) json.get(CHECKSUM_LENGTH_JSON)).intValue();
1022 }
1023
1024 @Override
1025 public byte[] getBytes() {
1026 return StringUtils.hexStringToByte((String) json.get(CHECKSUM_BYTES_JSON));
1027 }
1028
1029 @Override
1030 public void write(DataOutput out) throws IOException {
1031 throw new UnsupportedOperationException();
1032 }
1033
1034 @Override
1035 public void readFields(DataInput in) throws IOException {
1036 throw new UnsupportedOperationException();
1037 }
1038 };
1039 }
1040
1041
1042 @Override
1043 public Token<?> getDelegationToken(final String renewer)
1044 throws IOException {
1045 try {
1046 return UserGroupInformation.getCurrentUser().doAs(
1047 new PrivilegedExceptionAction<Token<?>>() {
1048 @Override
1049 public Token<?> run() throws Exception {
1050 return authURL.getDelegationToken(uri.toURL(), authToken,
1051 renewer);
1052 }
1053 }
1054 );
1055 } catch (Exception ex) {
1056 if (ex instanceof IOException) {
1057 throw (IOException) ex;
1058 } else {
1059 throw new IOException(ex);
1060 }
1061 }
1062 }
1063
1064 public long renewDelegationToken(final Token<?> token) throws IOException {
1065 try {
1066 return UserGroupInformation.getCurrentUser().doAs(
1067 new PrivilegedExceptionAction<Long>() {
1068 @Override
1069 public Long run() throws Exception {
1070 return authURL.renewDelegationToken(uri.toURL(), authToken);
1071 }
1072 }
1073 );
1074 } catch (Exception ex) {
1075 if (ex instanceof IOException) {
1076 throw (IOException) ex;
1077 } else {
1078 throw new IOException(ex);
1079 }
1080 }
1081 }
1082
1083 public void cancelDelegationToken(final Token<?> token) throws IOException {
1084 authURL.cancelDelegationToken(uri.toURL(), authToken);
1085 }
1086
1087 @Override
1088 public Token<?> getRenewToken() {
1089 return null; //TODO : for renewer
1090 }
1091
1092 @Override
1093 @SuppressWarnings("unchecked")
1094 public <T extends TokenIdentifier> void setDelegationToken(Token<T> token) {
1095 //TODO : for renewer
1096 }
1097
1098 @Override
1099 public void setXAttr(Path f, String name, byte[] value,
1100 EnumSet<XAttrSetFlag> flag) throws IOException {
1101 Map<String, String> params = new HashMap<String, String>();
1102 params.put(OP_PARAM, Operation.SETXATTR.toString());
1103 params.put(XATTR_NAME_PARAM, name);
1104 if (value != null) {
1105 params.put(XATTR_VALUE_PARAM,
1106 XAttrCodec.encodeValue(value, XAttrCodec.HEX));
1107 }
1108 params.put(XATTR_SET_FLAG_PARAM, EnumSetParam.toString(flag));
1109 HttpURLConnection conn = getConnection(Operation.SETXATTR.getMethod(),
1110 params, f, true);
1111 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1112 }
1113
1114 @Override
1115 public byte[] getXAttr(Path f, String name) throws IOException {
1116 Map<String, String> params = new HashMap<String, String>();
1117 params.put(OP_PARAM, Operation.GETXATTRS.toString());
1118 params.put(XATTR_NAME_PARAM, name);
1119 HttpURLConnection conn = getConnection(Operation.GETXATTRS.getMethod(),
1120 params, f, true);
1121 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1122 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
1123 Map<String, byte[]> xAttrs = createXAttrMap(
1124 (JSONArray) json.get(XATTRS_JSON));
1125 return xAttrs != null ? xAttrs.get(name) : null;
1126 }
1127
1128 /** Convert xAttrs json to xAttrs map */
1129 private Map<String, byte[]> createXAttrMap(JSONArray jsonArray)
1130 throws IOException {
1131 Map<String, byte[]> xAttrs = Maps.newHashMap();
1132 for (Object obj : jsonArray) {
1133 JSONObject jsonObj = (JSONObject) obj;
1134 final String name = (String)jsonObj.get(XATTR_NAME_JSON);
1135 final byte[] value = XAttrCodec.decodeValue(
1136 (String)jsonObj.get(XATTR_VALUE_JSON));
1137 xAttrs.put(name, value);
1138 }
1139
1140 return xAttrs;
1141 }
1142
1143 /** Convert xAttr names json to names list */
1144 private List<String> createXAttrNames(String xattrNamesStr) throws IOException {
1145 JSONParser parser = new JSONParser();
1146 JSONArray jsonArray;
1147 try {
1148 jsonArray = (JSONArray)parser.parse(xattrNamesStr);
1149 List<String> names = Lists.newArrayListWithCapacity(jsonArray.size());
1150 for (Object name : jsonArray) {
1151 names.add((String) name);
1152 }
1153 return names;
1154 } catch (ParseException e) {
1155 throw new IOException("JSON parser error, " + e.getMessage(), e);
1156 }
1157 }
1158
1159 @Override
1160 public Map<String, byte[]> getXAttrs(Path f) throws IOException {
1161 Map<String, String> params = new HashMap<String, String>();
1162 params.put(OP_PARAM, Operation.GETXATTRS.toString());
1163 HttpURLConnection conn = getConnection(Operation.GETXATTRS.getMethod(),
1164 params, f, true);
1165 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1166 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
1167 return createXAttrMap((JSONArray) json.get(XATTRS_JSON));
1168 }
1169
1170 @Override
1171 public Map<String, byte[]> getXAttrs(Path f, List<String> names)
1172 throws IOException {
1173 Preconditions.checkArgument(names != null && !names.isEmpty(),
1174 "XAttr names cannot be null or empty.");
1175 Map<String, String> params = new HashMap<String, String>();
1176 params.put(OP_PARAM, Operation.GETXATTRS.toString());
1177 Map<String, List<String>> multiValuedParams = Maps.newHashMap();
1178 multiValuedParams.put(XATTR_NAME_PARAM, names);
1179 HttpURLConnection conn = getConnection(Operation.GETXATTRS.getMethod(),
1180 params, multiValuedParams, f, true);
1181 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1182 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
1183 return createXAttrMap((JSONArray) json.get(XATTRS_JSON));
1184 }
1185
1186 @Override
1187 public List<String> listXAttrs(Path f) throws IOException {
1188 Map<String, String> params = new HashMap<String, String>();
1189 params.put(OP_PARAM, Operation.LISTXATTRS.toString());
1190 HttpURLConnection conn = getConnection(Operation.LISTXATTRS.getMethod(),
1191 params, f, true);
1192 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1193 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
1194 return createXAttrNames((String) json.get(XATTRNAMES_JSON));
1195 }
1196
1197 @Override
1198 public void removeXAttr(Path f, String name) throws IOException {
1199 Map<String, String> params = new HashMap<String, String>();
1200 params.put(OP_PARAM, Operation.REMOVEXATTR.toString());
1201 params.put(XATTR_NAME_PARAM, name);
1202 HttpURLConnection conn = getConnection(Operation.REMOVEXATTR.getMethod(),
1203 params, f, true);
1204 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1205 }
1206 }