blob: fc5ada0206a18694f21fd07f06d95e12f93b057d [file] [log] [blame]
/**
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: sligocki@google.com (Shawn Ligocki)
// jmarantz@google.com (Joshua Marantz)
#include "net/instaweb/rewriter/public/output_resource.h"
#include "base/logging.h"
#include "net/instaweb/rewriter/public/resource_namer.h"
#include "net/instaweb/rewriter/public/resource_manager.h"
#include "net/instaweb/rewriter/public/rewrite_filter.h"
#include "net/instaweb/util/public/content_type.h"
#include "net/instaweb/util/public/hasher.h"
#include "net/instaweb/util/public/file_system.h"
#include "net/instaweb/util/public/filename_encoder.h"
#include "net/instaweb/util/public/string_util.h"
#include "net/instaweb/util/public/string_writer.h"
#include "net/instaweb/util/stack_buffer.h"
namespace net_instaweb {
OutputResource::OutputResource(ResourceManager* manager,
const StringPiece& resolved_base,
const ResourceNamer& full_name,
const ContentType* type)
: Resource(manager, type),
output_file_(NULL),
writing_complete_(false),
generated_(false),
resolved_base_(resolved_base.data(), resolved_base.size()),
full_name_() {
full_name_.CopyFrom(full_name);
if (type == NULL) {
std::string ext_with_dot = StrCat(".", full_name.ext());
type_ = NameExtensionToContentType(ext_with_dot);
} else {
// This if + check used to be a 1-liner, but it was failing and this
// yields debuggable output.
// TODO(jmaessen): The addition of 1 below avoids the leading ".";
// make this convention consistent and fix all code.
CHECK_EQ((type->file_extension() + 1), full_name.ext());
}
}
OutputResource::~OutputResource() {
}
bool OutputResource::OutputWriter::Write(const StringPiece& data,
MessageHandler* handler) {
bool ret = http_value_->Write(data, handler);
if (file_writer_.get() != NULL) {
ret &= file_writer_->Write(data, handler);
}
return ret;
}
OutputResource::OutputWriter* OutputResource::BeginWrite(
MessageHandler* handler) {
value_.Clear();
full_name_.ClearHash();
CHECK(!writing_complete_);
CHECK(output_file_ == NULL);
if (resource_manager_->store_outputs_in_file_system()) {
FileSystem* file_system = resource_manager_->file_system();
// Always write to a tempfile, so that if we get interrupted in the middle
// we won't leave a half-baked file in the serving path.
std::string temp_prefix = TempPrefix();
output_file_ = file_system->OpenTempFile(
temp_prefix.c_str(), handler);
bool success = (output_file_ != NULL);
if (success) {
std::string header;
StringWriter string_writer(&header);
meta_data_.Write(&string_writer, handler); // Serialize header.
// It does not make sense to have the headers in the hash.
// call output_file_->Write directly, rather than going through
// OutputWriter.
//
// TODO(jmarantz): consider refactoring to split out the header-file
// writing in a different way, e.g. to a separate file.
success &= output_file_->Write(header, handler);
}
OutputWriter* writer = NULL;
if (success) {
writer = new OutputWriter(output_file_, &value_);
}
return writer;
} else {
return new OutputWriter(NULL, &value_);
};
}
bool OutputResource::EndWrite(OutputWriter* writer, MessageHandler* handler) {
CHECK(!writing_complete_);
value_.SetHeaders(meta_data_);
Hasher* hasher = resource_manager_->hasher();
full_name_.set_hash(hasher->Hash(contents()));
writing_complete_ = true;
if (output_file_ == NULL) {
return true;
} else {
FileSystem* file_system = resource_manager_->file_system();
CHECK(file_system != NULL);
std::string temp_filename = output_file_->filename();
bool ret = file_system->Close(output_file_, handler);
// Now that we are done writing, we can rename to the filename we
// really want.
if (ret) {
ret = file_system->RenameFile(temp_filename.c_str(), filename().c_str(),
handler);
}
// TODO(jmarantz): Consider writing to the HTTP cache as we write the
// file, so same-process write-then-read never has to read from disk.
// This is moderately inconvenient because HTTPCache lacks a streaming
// Put interface.
output_file_ = NULL;
delete writer;
return ret;
}
}
// Called by FilenameOutputResource::BeginWrite to determine how
// to start writing the tmpfile.
std::string OutputResource::TempPrefix() const {
return StrCat(resource_manager_->filename_prefix(), "temp_");
}
StringPiece OutputResource::suffix() const {
CHECK(type_ != NULL);
return type_->file_extension();
}
void OutputResource::set_suffix(const StringPiece& ext) {
type_ = NameExtensionToContentType(ext);
if (type_ != NULL) {
// TODO(jmaessen): The addition of 1 below avoids the leading ".";
// make this convention consistent and fix all code.
full_name_.set_ext(type_->file_extension() + 1);
} else {
full_name_.set_ext(ext.substr(1));
}
}
std::string OutputResource::filename() const {
std::string filename;
resource_manager_->filename_encoder()->Encode(
resource_manager_->filename_prefix(), url(), &filename);
return filename;
}
// TODO(jmarantz): change the name to reflect the fact that it is not
// just an accessor now.
std::string OutputResource::url() const {
std::string encoded = full_name_.Encode();
if (resolved_base_.empty()) {
// Resolves sharding, shoddily.
std::string base =
resource_manager_->UrlPrefixFor(full_name_);
encoded = StrCat(base, encoded);
} else {
// TODO(jmaessen): this is a band aid compensating for the fact that we
// aren't consistent about trailing / in resolved_base. If we believe we're
// always getting these from GoogleUrl::AllExceptLeaf they ought to lack the
// trailing /. But partnership->ResolvedBase() appears to have a trailing /
// in some circumstances (cf css_combine for examples that go wrong).
if (!resolved_base_.empty() &&
resolved_base_[resolved_base_.size() - 1] == '/') {
encoded = StrCat(resolved_base_, encoded);
} else {
encoded = StrCat(resolved_base_, "/", encoded);
}
}
return encoded;
}
void OutputResource::SetHash(const StringPiece& hash) {
CHECK(!writing_complete_);
CHECK(!has_hash());
full_name_.set_hash(hash);
}
bool OutputResource::ReadIfCached(MessageHandler* handler) {
if (!writing_complete_ && resource_manager_->store_outputs_in_file_system()) {
FileSystem* file_system = resource_manager_->file_system();
FileSystem::InputFile* file = file_system->OpenInputFile(
filename().c_str(), handler);
if (file != NULL) {
char buf[kStackBufferSize];
int nread = 0, num_consumed = 0;
// TODO(jmarantz): this logic is duplicated in util/wget_url_fetcher.cc,
// consider a refactor to merge it.
meta_data_.Clear();
value_.Clear();
while (!meta_data_.headers_complete() &&
((nread = file->Read(buf, sizeof(buf), handler)) != 0)) {
num_consumed = meta_data_.ParseChunk(
StringPiece(buf, nread), handler);
}
value_.SetHeaders(meta_data_);
writing_complete_ = value_.Write(
StringPiece(buf + num_consumed, nread - num_consumed),
handler);
while (writing_complete_ &&
((nread = file->Read(buf, sizeof(buf), handler)) != 0)) {
writing_complete_ = value_.Write(StringPiece(buf, nread), handler);
}
file_system->Close(file, handler);
}
}
return writing_complete_;
}
bool OutputResource::IsWritten() const {
return writing_complete_;
}
void OutputResource::SetType(const ContentType* content_type) {
Resource::SetType(content_type);
// TODO(jmaessen): The addition of 1 below avoids the leading ".";
// make this convention consistent and fix all code.
full_name_.set_ext(content_type->file_extension() + 1);
}
} // namespace net_instaweb