]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/s3website: Implement ErrorDoc & fix Double-Fault handler
authorRobin H. Johnson <robin.johnson@dreamhost.com>
Wed, 20 Apr 2016 22:52:51 +0000 (15:52 -0700)
committerRobin H. Johnson <robin.johnson@dreamhost.com>
Thu, 21 Apr 2016 22:06:39 +0000 (15:06 -0700)
Fix more last minute breakage from merges, now has has a working ErrorDoc as
well as working double-fault. Also moves some s3website-specific code out of
the main S3 codepath.

Fixes: #15532
Fixes: #15555
Signed-off-by: Robin H. Johnson <robin.johnson@dreamhost.com>
src/rgw/rgw_rest.cc
src/rgw/rgw_rest_s3.cc
src/rgw/rgw_rest_s3.h
src/rgw/rgw_rest_s3website.h

index a165b65692f6fde293585b60d52406e3d3e761bf..acd54b5738252a92246291fc30f1eb325694a5fc 100644 (file)
@@ -330,11 +330,7 @@ void set_req_state_err(struct rgw_err& err,     /* out */
 
   r = search_err(err_no, RGW_HTTP_ERRORS, ARRAY_LEN(RGW_HTTP_ERRORS));
   if (r) {
-    if (prot_flags & RGW_REST_WEBSITE && err_no == ERR_WEBSITE_REDIRECT && err.is_clear()) {
-      // http_ret was custom set, so don't change it!
-    } else {
-      err.http_ret = r->http_ret;
-    }
+    err.http_ret = r->http_ret;
     err.s3_code = r->s3_code;
     return;
   }
@@ -699,49 +695,56 @@ void abort_early(struct req_state *s, RGWOp *op, int err_no,
                      << " new_err_no=" << new_err_no << dendl;
     err_no = new_err_no;
   }
-  set_req_state_err(s, err_no);
-  dump_errno(s);
-  dump_bucket_from_state(s);
-  if (err_no == -ERR_PERMANENT_REDIRECT || err_no == -ERR_WEBSITE_REDIRECT) {
-    string dest_uri;
-    if (!s->redirect.empty()) {
-      dest_uri = s->redirect;
-    } else if (!s->zonegroup_endpoint.empty()) {
-      string dest_uri = s->zonegroup_endpoint;
-      /*
-       * reqest_uri is always start with slash, so we need to remove
-       * the unnecessary slash at the end of dest_uri.
-       */
-      if (dest_uri[dest_uri.size() - 1] == '/') {
-        dest_uri = dest_uri.substr(0, dest_uri.size() - 1);
+
+  // If the error handler(s) above dealt with it completely, they should have
+  // returned 0. If non-zero, we need to continue here.
+  if(err_no) {
+    // Watch out, we might have a custom error state already set!
+    if(s->err.http_ret && s->err.http_ret != 200) {
+      dump_errno(s);
+    } else {
+      set_req_state_err(s, err_no);
+      dump_errno(s);
+    }
+    dump_bucket_from_state(s);
+    if (err_no == -ERR_PERMANENT_REDIRECT || err_no == -ERR_WEBSITE_REDIRECT) {
+      string dest_uri;
+      if (!s->redirect.empty()) {
+        dest_uri = s->redirect;
+      } else if (!s->zonegroup_endpoint.empty()) {
+        string dest_uri = s->zonegroup_endpoint;
+        /*
+         * reqest_uri is always start with slash, so we need to remove
+         * the unnecessary slash at the end of dest_uri.
+         */
+        if (dest_uri[dest_uri.size() - 1] == '/') {
+          dest_uri = dest_uri.substr(0, dest_uri.size() - 1);
+        }
+        dest_uri += s->info.request_uri;
+        dest_uri += "?";
+        dest_uri += s->info.request_params;
+      }
+
+      if (!dest_uri.empty()) {
+        dump_redirect(s, dest_uri);
       }
-      dest_uri += s->info.request_uri;
-      dest_uri += "?";
-      dest_uri += s->info.request_params;
     }
 
-    if (!dest_uri.empty()) {
-      dump_redirect(s, dest_uri);
+    if (!error_content.empty()) {
+      /*
+       * TODO we must add all error entries as headers here:
+       * when having a working errordoc, then the s3 error fields are
+       * rendered as HTTP headers, e.g.:
+       *   x-amz-error-code: NoSuchKey
+       *   x-amz-error-message: The specified key does not exist.
+       *   x-amz-error-detail-Key: foo
+       */
+      end_header(s, op, NULL, error_content.size(), false, true);
+      STREAM_IO(s)->write(error_content.c_str(), error_content.size());
+    } else {
+      end_header(s, op);
     }
-  }
-  if (!error_content.empty()) {
-    ldout(s->cct, 20) << "error_content is set, we need to serve it INSTEAD"
-      " of firing the formatter" << dendl;
-    /*
-     * FIXME we must add all error entries as headers here:
-     * when having a working errordoc, then the s3 error fields are
-     * rendered as HTTP headers, e.g.:
-     *
-     *   x-amz-error-code: NoSuchKey
-     *   x-amz-error-message: The specified key does not exist.
-     *   x-amz-error-detail-Key: foo
-     */
-    end_header(s, op, NULL, NO_CONTENT_LENGTH, false, true);
-    STREAM_IO(s)->write(error_content.c_str(), error_content.size());
-    s->formatter->reset();
-  } else {
-    end_header(s, op);
-    rgw_flush_formatter_and_reset(s, s->formatter);
+    rgw_flush_formatter(s, s->formatter);
   }
   perfcounter->inc(l_rgw_failed_req);
 }
index 2ef10a5ed7426611b1a440dedb98be4108b54361..2ca415b094b5ae1f5028b52d0e60a73ed06c699b 100644 (file)
@@ -246,9 +246,14 @@ int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs,
   }
 
 done:
-  set_req_state_err(s, (partial_content && !op_ret) ? STATUS_PARTIAL_CONTENT
-                   : op_ret);
-  dump_errno(s);
+  if(custom_http_ret) {
+    set_req_state_err(s, 0);
+    dump_errno(s, custom_http_ret);
+  } else {
+    set_req_state_err(s, (partial_content && !op_ret) ? STATUS_PARTIAL_CONTENT
+                 : op_ret);
+    dump_errno(s);
+  }
 
   for (riter = response_attrs.begin(); riter != response_attrs.end();
        ++riter) {
@@ -3951,52 +3956,87 @@ RGWOp* RGWHandler_REST_S3Website::op_head()
   return get_obj_op(false);
 }
 
-int RGWHandler_REST_S3Website::get_errordoc(const string& errordoc_key,
-                                           std::string* error_content) {
-  ldout(s->cct, 20) << "TODO Serve Custom error page here if bucket has "
-    "<Error>" << dendl;
-  *error_content = errordoc_key;
-  // 1. Check if errordoc exists
-  // 2. Check if errordoc is public
-  // 3. Fetch errordoc content
-  /*
-   * FIXME maybe:  need to make sure all of the fields for conditional
-   * requests are cleared
-   */
-  RGWGetObj_ObjStore_S3Website* getop =
-    new RGWGetObj_ObjStore_S3Website(true);
-  getop->set_get_data(true);
-  getop->init(store, s, this);
+int RGWHandler_REST_S3Website::serve_errordoc(int http_ret, const string& errordoc_key) {
+  int ret = 0;
+  s->formatter->reset(); /* Try to throw it all away */
 
-  RGWGetObj_CB cb(getop);
-  rgw_obj obj(s->bucket, errordoc_key);
-  RGWObjectCtx rctx(store);
-  //RGWRados::Object op_target(store, s->bucket_info, *static_cast<RGWObjectCtx *>(s->obj_ctx), obj);
-  RGWRados::Object op_target(store, s->bucket_info, rctx, obj);
-  RGWRados::Object::Read read_op(&op_target);
+  RGWGetObj_ObjStore_S3Website* getop = (RGWGetObj_ObjStore_S3Website*) op_get();
+  if(!getop) {
+    return -1; // Trigger double error handler
+  }
+  getop->init(store, s, this);
+  getop->range_str = NULL;
+  getop->if_mod = NULL;
+  getop->if_unmod = NULL;
+  getop->if_match = NULL;
+  getop->if_nomatch = NULL;
+  s->object = errordoc_key;
+
+  ret = init_permissions(getop);
+  if (ret < 0) {
+    ldout(s->cct, 20) << "serve_errordoc failed, init_permissions ret=" << ret << dendl;
+    return -1; // Trigger double error handler
+  }
 
-  int ret;
-  int64_t ofs = 0; 
-  int64_t end = -1;
-  ret = read_op.prepare(&ofs, &end);
+  ret = read_permissions(getop);
   if (ret < 0) {
-    goto done;
+    ldout(s->cct, 20) << "serve_errordoc failed, read_permissions ret=" << ret << dendl;
+    return -1; // Trigger double error handler
   }
 
-  ret = read_op.iterate(ofs, end, &cb); // FIXME: need to know the final size?
-done:
-  delete getop;
-  return ret;
+  if(http_ret) {
+     getop->set_custom_http_response(http_ret);
+  }
+
+  ret = getop->init_processing();
+  if(ret < 0) {
+    ldout(s->cct, 20) << "serve_errordoc failed, init_processing ret=" << ret << dendl;
+    return -1; // Trigger double error handler
+  }
+
+  ret = getop->verify_op_mask();
+  if(ret < 0) {
+    ldout(s->cct, 20) << "serve_errordoc failed, verify_op_mask ret=" << ret << dendl;
+    return -1; // Trigger double error handler
+  }
+
+  ret = getop->verify_permission();
+  if(ret < 0) {
+    ldout(s->cct, 20) << "serve_errordoc failed, verify_permission ret=" << ret << dendl;
+    return -1; // Trigger double error handler
+  }
+
+  ret = getop->verify_params();
+  if(ret < 0) {
+    ldout(s->cct, 20) << "serve_errordoc failed, verify_params ret=" << ret << dendl;
+    return -1; // Trigger double error handler
+  }
+
+  // No going back now
+  getop->pre_exec();
+  /*
+   * FIXME Missing headers:
+   * With a working errordoc, the s3 error fields are rendered as HTTP headers,
+   *   x-amz-error-code: NoSuchKey
+   *   x-amz-error-message: The specified key does not exist.
+   *   x-amz-error-detail-Key: foo
+   */
+  getop->execute();
+  getop->complete();
+  return 0;
+
 }
-  
+
 int RGWHandler_REST_S3Website::error_handler(int err_no,
                                            string* error_content) {
+  int new_err_no = -1;
   const struct rgw_http_errors* r;
   int http_error_code = -1;
-  r = search_err(err_no, RGW_HTTP_ERRORS, ARRAY_LEN(RGW_HTTP_ERRORS));
+  r = search_err(err_no > 0 ? err_no : -err_no, RGW_HTTP_ERRORS, ARRAY_LEN(RGW_HTTP_ERRORS));
   if (r) {
     http_error_code = r->http_ret;
   }
+  ldout(s->cct, 10) << "RGWHandler_REST_S3Website::error_handler err_no=" << err_no << " http_ret=" << http_error_code << dendl;
 
   RGWBWRoutingRule rrule;
   bool should_redirect =
@@ -4017,9 +4057,18 @@ int RGWHandler_REST_S3Website::error_handler(int err_no,
                      << " proto+host:" << protocol << "://" << hostname
                      << " -> " << s->redirect << dendl;
     return -ERR_WEBSITE_REDIRECT;
+  } else if (err_no == -ERR_WEBSITE_REDIRECT) {
+    // Do nothing here, this redirect will be handled in abort_early's ERR_WEBSITE_REDIRECT block
+    // Do NOT fire the ErrorDoc handler
   } else if (!s->bucket_info.website_conf.error_doc.empty()) {
-    RGWHandler_REST_S3Website::get_errordoc(
-      s->bucket_info.website_conf.error_doc, error_content);
+    /* This serves an entire page!
+       On success, it will return zero, and no further content should be sent to the socket
+       On failure, we need the double-error handler
+     */
+    new_err_no = RGWHandler_REST_S3Website::serve_errordoc(http_error_code, s->bucket_info.website_conf.error_doc);
+    if(new_err_no && new_err_no != -1) {
+      err_no = new_err_no;
+    }
   } else {
     ldout(s->cct, 20) << "No special error handling today!" << dendl;
   }
index 9278964d34f6d737b1190afcc2b2d7e789f5ebe0..1482a95287f001779b3f7b3023e7528253e5d813 100644 (file)
@@ -22,12 +22,17 @@ void rgw_get_errno_s3(struct rgw_http_errors *e, int err_no);
 
 class RGWGetObj_ObjStore_S3 : public RGWGetObj_ObjStore
 {
+protected:
+  // Serving a custom error page from an object is really a 200 response with
+  // just the status line altered.
+  int custom_http_ret = 0;
 public:
   RGWGetObj_ObjStore_S3() {}
   ~RGWGetObj_ObjStore_S3() {}
 
   int send_response_data_error();
   int send_response_data(bufferlist& bl, off_t ofs, off_t len);
+  void set_custom_http_response(int http_ret) { custom_http_ret = http_ret; }
 };
 
 class RGWListBuckets_ObjStore_S3 : public RGWListBuckets_ObjStore {
@@ -461,8 +466,6 @@ public:
   RGWHandler_REST_S3() : RGWHandler_REST() {}
   virtual ~RGWHandler_REST_S3() {}
 
-  int get_errordoc(const string& errordoc_key, string* error_content);  
-
   virtual int init(RGWRados *store, struct req_state *s, RGWClientIO *cio);
   virtual int authorize() {
     return RGW_Auth_S3::authorize(store, s);
index b14942bfd45a289cbb38edb912c348cdb06154f7..943eabe1392e00c140137383b25eeef1e296b778 100644 (file)
@@ -32,7 +32,7 @@ protected:
   RGWOp *op_copy() { return NULL; }
   RGWOp *op_options() { return NULL; }
 
-  int get_errordoc(const string& errordoc_key, string *error_content);
+  int serve_errordoc(int http_ret, const string &errordoc_key);
 public:
   RGWHandler_REST_S3Website() : RGWHandler_REST_S3() {}
   virtual ~RGWHandler_REST_S3Website() {}
@@ -69,6 +69,7 @@ public:
 // TODO: do we actually need this?
 class  RGWGetObj_ObjStore_S3Website : public RGWGetObj_ObjStore_S3
 {
+  friend class RGWHandler_REST_S3Website;
 private:
    bool is_errordoc_request;
 public:
@@ -86,7 +87,7 @@ public:
         if_unmod = NULL;
         if_match = NULL;
         if_nomatch = NULL;
-               return 0;
+        return 0;
       } else {
         return RGWGetObj_ObjStore_S3::get_params();
       }