Bug 569177 - Add support for eTag for WebServices
r/a=glob git-svn-id: svn://10.0.0.236/trunk@264967 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
parent
7538504a69
commit
e1ba79275b
@ -1 +1 @@
|
|||||||
8698
|
8699
|
||||||
@ -236,11 +236,11 @@ sub check_etag {
|
|||||||
$possible_etag =~ s/^\"//g;
|
$possible_etag =~ s/^\"//g;
|
||||||
$possible_etag =~ s/\"$//g;
|
$possible_etag =~ s/\"$//g;
|
||||||
if ($possible_etag eq $valid_etag or $possible_etag eq '*') {
|
if ($possible_etag eq $valid_etag or $possible_etag eq '*') {
|
||||||
print $self->header(-ETag => $possible_etag,
|
return 1;
|
||||||
-status => '304 Not Modified');
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Have to add the cookies in.
|
# Have to add the cookies in.
|
||||||
|
|||||||
@ -349,6 +349,18 @@ sub get {
|
|||||||
push(@bugs, $self->_bug_to_hash($bug, $params));
|
push(@bugs, $self->_bug_to_hash($bug, $params));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Set the ETag before inserting the update tokens
|
||||||
|
# since the tokens will always be unique even if
|
||||||
|
# the data has not changed.
|
||||||
|
$self->bz_etag(\@bugs);
|
||||||
|
|
||||||
|
if (Bugzilla->user->id) {
|
||||||
|
foreach my $bug (@bugs) {
|
||||||
|
my $token = issue_hash_token([$bug->{'id'}, $bug->{'last_change_time'}]);
|
||||||
|
$bug->{'update_token'} = $self->type('string', $token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { bugs => \@bugs, faults => \@faults };
|
return { bugs => \@bugs, faults => \@faults };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -986,11 +998,6 @@ sub _bug_to_hash {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Bugzilla->user->id) {
|
|
||||||
my $token = issue_hash_token([$bug->id, $bug->delta_ts]);
|
|
||||||
$item{'update_token'} = $self->type('string', $token);
|
|
||||||
}
|
|
||||||
|
|
||||||
# The "accessible" bits go here because they have long names and it
|
# The "accessible" bits go here because they have long names and it
|
||||||
# makes the code look nicer to separate them out.
|
# makes the code look nicer to separate them out.
|
||||||
$item{'is_cc_accessible'} = $self->type('boolean',
|
$item{'is_cc_accessible'} = $self->type('boolean',
|
||||||
|
|||||||
@ -14,6 +14,9 @@ use Bugzilla::Error;
|
|||||||
use Bugzilla::Util qw(datetime_from);
|
use Bugzilla::Util qw(datetime_from);
|
||||||
|
|
||||||
use Scalar::Util qw(blessed);
|
use Scalar::Util qw(blessed);
|
||||||
|
use Digest::MD5 qw(md5_base64);
|
||||||
|
|
||||||
|
use Storable qw(freeze);
|
||||||
|
|
||||||
sub handle_login {
|
sub handle_login {
|
||||||
my ($self, $class, $method, $full_method) = @_;
|
my ($self, $class, $method, $full_method) = @_;
|
||||||
@ -55,8 +58,63 @@ sub datetime_format_outbound {
|
|||||||
return $time->iso8601();
|
return $time->iso8601();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ETag support
|
||||||
|
sub bz_etag {
|
||||||
|
my ($self, $data) = @_;
|
||||||
|
my $cache = Bugzilla->request_cache;
|
||||||
|
if (defined $data) {
|
||||||
|
# Serialize the data if passed a reference
|
||||||
|
local $Storable::canonical = 1;
|
||||||
|
$data = freeze($data) if ref $data;
|
||||||
|
|
||||||
|
# Wide characters cause md5_base64() to die.
|
||||||
|
utf8::encode($data) if utf8::is_utf8($data);
|
||||||
|
|
||||||
|
# Append content_type to the end of the data
|
||||||
|
# string as we want the etag to be unique to
|
||||||
|
# the content_type. We do not need this for
|
||||||
|
# XMLRPC as text/xml is always returned.
|
||||||
|
if (blessed($self) && $self->can('content_type')) {
|
||||||
|
$data .= $self->content_type if $self->content_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache->{'bz_etag'} = md5_base64($data);
|
||||||
|
}
|
||||||
|
return $cache->{'bz_etag'};
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
Bugzilla::WebService::Server - Base server class for the WebService API
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
Bugzilla::WebService::Server is the base class for the individual WebService API
|
||||||
|
servers such as XMLRPC, JSONRPC, and REST. You never actually create a
|
||||||
|
Bugzilla::WebService::Server directly, you only make subclasses of it.
|
||||||
|
|
||||||
|
=head1 FUNCTIONS
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item C<bz_etag>
|
||||||
|
|
||||||
|
This function is used to store an ETag value that will be used when returning
|
||||||
|
the data by the different API server modules such as XMLRPC, or REST. The individual
|
||||||
|
webservice methods can also set the value earlier in the process if needed such as
|
||||||
|
before a unique update token is added. If a value is not set earlier, an etag will
|
||||||
|
automatically be created using the returned data except in some cases when an error
|
||||||
|
has occurred.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=head1 SEE ALSO
|
||||||
|
|
||||||
|
L<Bugzilla::WebService::Server::XMLRPC|XMLRPC>, L<Bugzilla::WebService::Server::JSONRPC|JSONRPC>,
|
||||||
|
and L<Bugzilla::WebService::Server::REST|REST>.
|
||||||
|
|
||||||
=head1 B<Methods in need of POD>
|
=head1 B<Methods in need of POD>
|
||||||
|
|
||||||
=over
|
=over
|
||||||
|
|||||||
@ -75,12 +75,12 @@ sub response_header {
|
|||||||
|
|
||||||
sub response {
|
sub response {
|
||||||
my ($self, $response) = @_;
|
my ($self, $response) = @_;
|
||||||
|
my $cgi = $self->cgi;
|
||||||
|
|
||||||
# Implement JSONP.
|
# Implement JSONP.
|
||||||
if (my $callback = $self->_bz_callback) {
|
if (my $callback = $self->_bz_callback) {
|
||||||
my $content = $response->content;
|
my $content = $response->content;
|
||||||
$response->content("$callback($content)");
|
$response->content("$callback($content)");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Use $cgi->header properly instead of just printing text directly.
|
# Use $cgi->header properly instead of just printing text directly.
|
||||||
@ -95,9 +95,18 @@ sub response {
|
|||||||
push(@header_args, "-$name", $value);
|
push(@header_args, "-$name", $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
my $cgi = $self->cgi;
|
|
||||||
|
# ETag support
|
||||||
|
my $etag = $self->bz_etag;
|
||||||
|
if ($etag && $cgi->check_etag($etag)) {
|
||||||
|
push(@header_args, "-ETag", $etag);
|
||||||
|
print $cgi->header(-status => '304 Not Modified', @header_args);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
push(@header_args, "-ETag", $etag) if $etag;
|
||||||
print $cgi->header(-status => $response->code, @header_args);
|
print $cgi->header(-status => $response->code, @header_args);
|
||||||
print $response->content;
|
print $response->content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# The JSON-RPC 1.1 GET specification is not so great--you can't specify
|
# The JSON-RPC 1.1 GET specification is not so great--you can't specify
|
||||||
@ -257,7 +266,17 @@ sub _handle {
|
|||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($obj) = @_;
|
my ($obj) = @_;
|
||||||
$self->{_bz_request_id} = $obj->{id};
|
$self->{_bz_request_id} = $obj->{id};
|
||||||
return $self->SUPER::_handle(@_);
|
|
||||||
|
my $result = $self->SUPER::_handle(@_);
|
||||||
|
|
||||||
|
# Set the ETag if not already set in the webservice methods.
|
||||||
|
my $etag = $self->bz_etag;
|
||||||
|
if (!$etag && ref $result) {
|
||||||
|
my $data = $self->json->decode($result)->{'result'};
|
||||||
|
$self->bz_etag($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Make all error messages returned by JSON::RPC go into the 100000
|
# Make all error messages returned by JSON::RPC go into the 100000
|
||||||
|
|||||||
@ -125,6 +125,10 @@ sub response {
|
|||||||
# Access Control
|
# Access Control
|
||||||
$response->header("Access-Control-Allow-Origin", "*");
|
$response->header("Access-Control-Allow-Origin", "*");
|
||||||
|
|
||||||
|
# ETag support
|
||||||
|
my $etag = $self->bz_etag;
|
||||||
|
$self->bz_etag($result) if !$etag;
|
||||||
|
|
||||||
# If accessing through web browser, then display in readable format
|
# If accessing through web browser, then display in readable format
|
||||||
if ($self->content_type eq 'text/html') {
|
if ($self->content_type eq 'text/html') {
|
||||||
$result = $self->json->pretty->canonical->encode($result);
|
$result = $self->json->pretty->canonical->encode($result);
|
||||||
|
|||||||
@ -21,8 +21,8 @@ if ($ENV{MOD_PERL}) {
|
|||||||
use Bugzilla::WebService::Constants;
|
use Bugzilla::WebService::Constants;
|
||||||
use Bugzilla::Util;
|
use Bugzilla::Util;
|
||||||
|
|
||||||
# Allow WebService methods to call XMLRPC::Lite's type method directly
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
|
# Allow WebService methods to call XMLRPC::Lite's type method directly
|
||||||
*Bugzilla::WebService::type = sub {
|
*Bugzilla::WebService::type = sub {
|
||||||
my ($self, $type, $value) = @_;
|
my ($self, $type, $value) = @_;
|
||||||
if ($type eq 'dateTime') {
|
if ($type eq 'dateTime') {
|
||||||
@ -39,6 +39,11 @@ BEGIN {
|
|||||||
}
|
}
|
||||||
return XMLRPC::Data->type($type)->value($value);
|
return XMLRPC::Data->type($type)->value($value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Add support for ETags into XMLRPC WebServices
|
||||||
|
*Bugzilla::WebService::bz_etag = sub {
|
||||||
|
return Bugzilla::WebService::Server->bz_etag($_[1]);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
sub initialize {
|
sub initialize {
|
||||||
@ -52,22 +57,38 @@ sub initialize {
|
|||||||
|
|
||||||
sub make_response {
|
sub make_response {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
my $cgi = Bugzilla->cgi;
|
||||||
|
|
||||||
$self->SUPER::make_response(@_);
|
$self->SUPER::make_response(@_);
|
||||||
|
|
||||||
# XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
|
# XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
|
||||||
# its cookies in Bugzilla::CGI, so we need to copy them over.
|
# its cookies in Bugzilla::CGI, so we need to copy them over.
|
||||||
foreach my $cookie (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
|
foreach my $cookie (@{$cgi->{'Bugzilla_cookie_list'}}) {
|
||||||
$self->response->headers->push_header('Set-Cookie', $cookie);
|
$self->response->headers->push_header('Set-Cookie', $cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Copy across security related headers from Bugzilla::CGI
|
# Copy across security related headers from Bugzilla::CGI
|
||||||
foreach my $header (split(/[\r\n]+/, Bugzilla->cgi->header)) {
|
foreach my $header (split(/[\r\n]+/, $cgi->header)) {
|
||||||
my ($name, $value) = $header =~ /^([^:]+): (.*)/;
|
my ($name, $value) = $header =~ /^([^:]+): (.*)/;
|
||||||
if (!$self->response->headers->header($name)) {
|
if (!$self->response->headers->header($name)) {
|
||||||
$self->response->headers->header($name => $value);
|
$self->response->headers->header($name => $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ETag support
|
||||||
|
my $etag = $self->bz_etag;
|
||||||
|
if (!$etag) {
|
||||||
|
my $data = $self->response->as_string;
|
||||||
|
$etag = $self->bz_etag($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($etag && $cgi->check_etag($etag)) {
|
||||||
|
$self->response->headers->push_header('ETag', $etag);
|
||||||
|
$self->response->headers->push_header('status', '304 Not Modified');
|
||||||
|
}
|
||||||
|
elsif ($etag) {
|
||||||
|
$self->response->headers->push_header('ETag', $etag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub handle_login {
|
sub handle_login {
|
||||||
|
|||||||
@ -142,7 +142,11 @@ sub display_data {
|
|||||||
utf8::encode($digest_data) if utf8::is_utf8($digest_data);
|
utf8::encode($digest_data) if utf8::is_utf8($digest_data);
|
||||||
my $digest = md5_base64($digest_data);
|
my $digest = md5_base64($digest_data);
|
||||||
|
|
||||||
$cgi->check_etag($digest);
|
if ($cgi->check_etag($digest)) {
|
||||||
|
print $cgi->header(-ETag => $digest,
|
||||||
|
-status => '304 Not Modified');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
print $cgi->header (-ETag => $digest,
|
print $cgi->header (-ETag => $digest,
|
||||||
-type => $format->{'ctype'});
|
-type => $format->{'ctype'});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user