commit fcb3cf169f1fa7cf878eb154dc3f1ff78e278056 Author: Robin H. Johnson Date: Wed Apr 20 15:52:51 2016 -0700 rgw/s3website: Implement ErrorDoc & fix Double-Fault handler 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 diff --git a/src/rgw/rgw_rest.cc b/src/rgw/rgw_rest.cc index a165b65..acd54b5 100644 --- a/src/rgw/rgw_rest.cc +++ b/src/rgw/rgw_rest.cc @@ -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.s3_code = r->s3_code; return; } @@ -699,8 +695,17 @@ void abort_early(struct req_state *s, RGWOp *op, int err_no, << " new_err_no=" << new_err_no << dendl; err_no = new_err_no; } + + // 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; @@ -724,24 +729,22 @@ void abort_early(struct req_state *s, RGWOp *op, int err_no, dump_redirect(s, dest_uri); } } + 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: + * 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, NO_CONTENT_LENGTH, false, true); + end_header(s, op, NULL, error_content.size(), 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); } diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 2ef10a5..2ca415b 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -246,9 +246,14 @@ int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs, } done: + 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 " - "" << 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); +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_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 + } - RGWGetObj_CB cb(getop); - rgw_obj obj(s->bucket, errordoc_key); - RGWObjectCtx rctx(store); - //RGWRados::Object op_target(store, s->bucket_info, *static_cast(s->obj_ctx), obj); - RGWRados::Object op_target(store, s->bucket_info, rctx, obj); - RGWRados::Object::Read read_op(&op_target); + ret = read_permissions(getop); + if (ret < 0) { + ldout(s->cct, 20) << "serve_errordoc failed, read_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); + if(http_ret) { + getop->set_custom_http_response(http_ret); + } + + ret = getop->init_processing(); if(ret < 0) { - goto done; + ldout(s->cct, 20) << "serve_errordoc failed, init_processing 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; + 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; } diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index 9278964..1482a95 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -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); diff --git a/src/rgw/rgw_rest_s3website.h b/src/rgw/rgw_rest_s3website.h index b14942b..943eabe 100644 --- a/src/rgw/rgw_rest_s3website.h +++ b/src/rgw/rgw_rest_s3website.h @@ -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: