]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 432907: Create a JSON frontend for WebServices
authormkanat%bugzilla.org <>
Tue, 31 Mar 2009 06:37:53 +0000 (06:37 +0000)
committermkanat%bugzilla.org <>
Tue, 31 Mar 2009 06:37:53 +0000 (06:37 +0000)
Patch by Max Kanat-Alexander <mkanat@bugzilla.org> r=dkl, a=mkanat

17 files changed:
Bugzilla.pm
Bugzilla/CGI.pm
Bugzilla/Constants.pm
Bugzilla/Error.pm
Bugzilla/Install/Requirements.pm
Bugzilla/Object.pm
Bugzilla/WebService.pm
Bugzilla/WebService/Bug.pm
Bugzilla/WebService/Constants.pm
Bugzilla/WebService/README [new file with mode: 0644]
Bugzilla/WebService/Server/JSONRPC.pm [new file with mode: 0644]
Bugzilla/WebService/Server/XMLRPC.pm
extensions/example/lib/WSExample.pm
jsonrpc.cgi [new file with mode: 0644]
template/en/default/global/code-error.html.tmpl
template/en/default/global/user-error.html.tmpl
xmlrpc.cgi

index 324b3cc145b8a940799d5c1b373e7c8403f282a0..7bd40794a4303af95a08fb2ef41baf238b64b8e8 100644 (file)
@@ -360,6 +360,15 @@ sub error_mode {
         || (i_am_cgi() ? ERROR_MODE_WEBPAGE : ERROR_MODE_DIE);
 }
 
+# This is used only by Bugzilla::Error to throw errors.
+sub _json_server {
+    my ($class, $newval) = @_;
+    if (defined $newval) {
+        $class->request_cache->{_json_server} = $newval;
+    }
+    return $class->request_cache->{_json_server};
+}
+
 sub usage_mode {
     my ($class, $newval) = @_;
     if (defined $newval) {
@@ -369,9 +378,12 @@ sub usage_mode {
         elsif ($newval == USAGE_MODE_CMDLINE) {
             $class->error_mode(ERROR_MODE_DIE);
         }
-        elsif ($newval == USAGE_MODE_WEBSERVICE) {
+        elsif ($newval == USAGE_MODE_XMLRPC) {
             $class->error_mode(ERROR_MODE_DIE_SOAP_FAULT);
         }
+        elsif ($newval == USAGE_MODE_JSON) {
+            $class->error_mode(ERROR_MODE_JSON_RPC);
+        }
         elsif ($newval == USAGE_MODE_EMAIL) {
             $class->error_mode(ERROR_MODE_DIE);
         }
@@ -667,10 +679,11 @@ usage mode changes.
 =item C<usage_mode>
 
 Call either C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_CMDLINE)>
-or C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_WEBSERVICE)> near the
+or C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_XMLRPC)> near the
 beginning of your script to change this flag's default of
 C<Bugzilla::Constants::USAGE_MODE_BROWSER> and to indicate that Bugzilla is
 being called in a non-interactive manner.
+
 This influences error handling because on usage mode changes, C<usage_mode>
 calls C<Bugzilla->error_mode> to set an error mode which makes sense for the
 usage mode.
index d7934f89b5c3cde7e394d477e60eff1f7758dae1..4af6a933d36ecd3e552823a8cc99051ec15a897c 100644 (file)
@@ -347,10 +347,10 @@ sub require_https {
     my ($self, $url) = @_;
     # Do not create query string if data submitted via XMLRPC
     # since we want the data to be resubmitted over POST method.
-    my $query = Bugzilla->usage_mode == USAGE_MODE_WEBSERVICE ? 0 : 1;
+    my $query = Bugzilla->usage_mode == USAGE_MODE_XMLRPC ? 0 : 1;
     # XMLRPC clients (SOAP::Lite at least) requires 301 to redirect properly
     # and do not work with 302.
-    my $status = Bugzilla->usage_mode == USAGE_MODE_WEBSERVICE ? 301 : 302;
+    my $status = Bugzilla->usage_mode == USAGE_MODE_XMLRPC ? 301 : 302;
     if (defined $url) {
         $url .= $self->url('-path_info' => 1, '-query' => $query, '-relative' => 1);
     } else {
index 858464542cacb9d413826e20f0134c6f81128ef8..0150f3124c032535dcb5fd4256846455b0668aa9 100644 (file)
@@ -128,12 +128,14 @@ use File::Basename;
 
     USAGE_MODE_BROWSER
     USAGE_MODE_CMDLINE
-    USAGE_MODE_WEBSERVICE
+    USAGE_MODE_XMLRPC
     USAGE_MODE_EMAIL
+    USAGE_MODE_JSON
 
     ERROR_MODE_WEBPAGE
     ERROR_MODE_DIE
     ERROR_MODE_DIE_SOAP_FAULT
+    ERROR_MODE_JSON_RPC
 
     INSTALLATION_MODE_INTERACTIVE
     INSTALLATION_MODE_NON_INTERACTIVE
@@ -378,14 +380,16 @@ use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
 # Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode.
 use constant USAGE_MODE_BROWSER    => 0;
 use constant USAGE_MODE_CMDLINE    => 1;
-use constant USAGE_MODE_WEBSERVICE => 2;
+use constant USAGE_MODE_XMLRPC     => 2;
 use constant USAGE_MODE_EMAIL      => 3;
+use constant USAGE_MODE_JSON       => 4;
 
 # Error modes. Default set by Bugzilla->usage_mode (so ERROR_MODE_WEBPAGE
 # usually). Use with Bugzilla->error_mode.
 use constant ERROR_MODE_WEBPAGE        => 0;
 use constant ERROR_MODE_DIE            => 1;
 use constant ERROR_MODE_DIE_SOAP_FAULT => 2;
+use constant ERROR_MODE_JSON_RPC       => 3;
 
 # The various modes that checksetup.pl can run in.
 use constant INSTALLATION_MODE_INTERACTIVE => 0;
index d15336a81c751b3ac75a6e8e2e92124f42fbe32e..661c72f740738aea56367ee05b9f871c3d452f55 100644 (file)
@@ -101,7 +101,9 @@ sub _throw_error {
         if (Bugzilla->error_mode == ERROR_MODE_DIE) {
             die("$message\n");
         }
-        elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
+        elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT
+               || Bugzilla->error_mode == ERROR_MODE_JSON_RPC)
+        {
             # Clone the hash so we aren't modifying the constant.
             my %error_map = %{ WS_ERROR_CODE() };
             require Bugzilla::Hook;
@@ -112,7 +114,19 @@ sub _throw_error {
                 $code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
                 $code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
             }
-            die SOAP::Fault->faultcode($code)->faultstring($message);
+
+            if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
+                die SOAP::Fault->faultcode($code)->faultstring($message);
+            }
+            else {
+                my $server = Bugzilla->_json_server;
+                # Technically JSON-RPC isn't allowed to have error numbers
+                # higher than 999, but we do this to avoid conflicts with
+                # the internal JSON::RPC error codes.
+                $server->raise_error(code    => 100000 + $code,
+                                     message => $message);
+                $server->response($server->error_response_header);
+            }
         }
     }
     exit;
index 4f17ae0535ec60f10462478fd2379a177fbe7d37..dba588c8a659b5ad5c2c6fc8de721392d54aa68d 100644 (file)
@@ -225,6 +225,12 @@ sub OPTIONAL_MODULES {
         blacklist => ['^0\.70', '^0\.710?\.0[1-5]$'],
         feature => 'XML-RPC Interface'
     },
+    {
+        package => 'JSON-RPC',
+        module  => 'JSON::RPC',
+        version => 0,
+        feature => 'JSON-RPC Interface'
+    },
     {
         # We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
         package => 'HTML-Parser',
index adc96fa503a871375e72c8e65901fc67efeeb659..6cca49e45b4e62ebaf3e52820ad8bf8f70731ba2 100644 (file)
@@ -219,7 +219,12 @@ sub _do_list_select {
     $sql .= " $postamble" if $postamble;
         
     my $dbh = Bugzilla->dbh;
-    my $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @$values);
+    # Sometimes the values are tainted, but we don't want to untaint them
+    # for the caller. So we copy the array. It's safe to untaint because
+    # they're only used in placeholders here.
+    my @untainted = @{ $values || [] };
+    trick_taint($_) foreach @untainted;
+    my $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @untainted);
     bless ($_, $class) foreach @$objects;
     return $objects
 }
index 735291fc3465e6eb0b28e70ed4290cff9e24fbd5..ffaf7c4b7ba2ae6e4d2734287eaa32eb12958b4d 100755 (executable)
@@ -22,17 +22,8 @@ use strict;
 use Date::Parse;
 use XMLRPC::Lite;
 
-sub datetime_format {
-    my ($self, $date_string) = @_;
-
-    my $time = str2time($date_string);
-    my ($sec, $min, $hour, $mday, $mon, $year) = localtime $time;
-    # This format string was stolen from SOAP::Utils->format_datetime,
-    # which doesn't work but which has almost the right format string.
-    my $iso_datetime = sprintf('%d%02d%02dT%02d:%02d:%02d',
-        $year + 1900, $mon + 1, $mday, $hour, $min, $sec);
-    return $iso_datetime;
-}
+# Used by the JSON-RPC server to convert incoming date fields apprpriately.
+use constant DATE_FIELDS => {};
 
 # For some methods, we shouldn't call Bugzilla->login before we call them
 use constant LOGIN_EXEMPT => { };
@@ -63,85 +54,84 @@ Bugzilla::WebService - The Web Service interface to Bugzilla
 This is the standard API for external programs that want to interact
 with Bugzilla. It provides various methods in various modules.
 
-Currently the only method of accessing the API is via XML-RPC. The XML-RPC
-standard is described here: L<http://www.xmlrpc.com/spec>
-
-The endpoint for Bugzilla WebServices is the C<xmlrpc.cgi> script in
-your Bugzilla installation. For example, if your Bugzilla is at
-C<bugzilla.yourdomain.com>, then your XML-RPC client would access the
-API via: C<http://bugzilla.yourdomain.com/xmlrpc.cgi>
+You can interact with this API via
+L<XML-RPC|Bugzilla::WebService::Server::XMLRPC> or
+L<JSON-RPC|Bugzilla::WebService::Server::JSONRPC>.
 
 =head1 CALLING METHODS
 
-Methods are called in the normal XML-RPC fashion. Bugzilla does not currently
-implement any extensions to the standard method of XML-RPC method calling.
-
 Methods are grouped into "packages", like C<Bug> for 
 L<Bugzilla::WebService::Bug>. So, for example,
-L<Bugzilla::WebService::Bug/get>, is called as C<Bug.get> in XML-RPC.
+L<Bugzilla::WebService::Bug/get>, is called as C<Bug.get>.
 
 =head1 PARAMETERS
 
-In addition to the standard parameter types like C<int>, C<string>, etc.,
-XML-RPC has two data structures, a C<< <struct> >> and an C<< <array> >>.
+The Bugzilla API takes the following various types of parameters:
+
+=over
 
-=head2 Structs
+=item C<int>
 
-In Perl, we call a C<< <struct> >> a "hash" or a "hashref". You may see
-us refer to it that way in the API documentation.
+Integer. May be null.
 
-In example code, you will see the characters C<{> and C<}> used to represent
-the beginning and end of structs.
+=item C<double>
 
-For example, here's a struct in XML-RPC:
+A floating-point number. May be null.
 
- <struct>
-   <member>
-     <name>fruit</name>
-     <value><string>oranges</string></value>
-   </member>
-   <member>
-     <name>vegetable</name>
-     <value><string>lettuce</string></value>
-   </member>
- </struct>
+=item C<string>
 
-In our example code in these API docs, that would look like:
+A string. May be null.
 
- { fruit => 'oranges', vegetable => 'lettuce' }
+=item C<dateTime>
 
-=head2 Arrays
+A date/time. Represented differently in different interfaces to this API.
+May be null.
+
+=item C<boolean>
+
+True or false.
+
+=item C<array>
+
+An array. There may be mixed types in an array.
 
 In example code, you will see the characters C<[> and C<]> used to
 represent the beginning and end of arrays.
 
-For example, here's an array in XML-RPC:
+In our example code in these API docs, an array that contains the numbers
+1, 2, and 3 would look like:
 
- <array>
-   <data>
-     <value><i4>1</i4></value>
-     <value><i4>2</i4></value>
-     <value><i4>3</i4></value>
-   </data>
- </array>
+ [1, 2, 3]
 
-In our example code in these API docs, that would look like:
+=item C<struct>
 
- [1, 2, 3]
+A mapping of keys to values. Called a "hash", "dict", or "map" in some
+other programming languages. We sometimes call this a "hash" in the API
+documentation.
+
+The keys are strings, and the values can be any type.
+
+In example code, you will see the characters C<{> and C<}> used to represent
+the beginning and end of structs.
+
+For example, a struct with an "fruit" key whose value is "oranges",
+and a "vegetable" key whose value is "lettuce" would look like:
+
+ { fruit => 'oranges', vegetable => 'lettuce' }
+
+=back
 
 =head2 How Bugzilla WebService Methods Take Parameters
 
-B<All> Bugzilla WebServices functions take their parameters in
-a C<< <struct> >>. Another way of saying this would be: All functions
-take a single argument, a C<< <struct> >> that contains all parameters.
-The names of the parameters listed in the API docs for each function are
-the C<name> element for the struct C<member>s.
+B<All> Bugzilla WebService functions use I<named> parameters.
+The individual C<Bugzilla::WebService::Server> modules explain
+how this is implemented for those frontends.
 
 =head1 LOGGING IN
 
 You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla 
 user. This issues standard HTTP cookies that you must then use in future
-calls, so your XML-RPC client must be capable of receiving and transmitting
+calls, so your client must be capable of receiving and transmitting
 cookies.
 
 =head1 STABLE, EXPERIMENTAL, and UNSTABLE
@@ -164,18 +154,17 @@ Bugzilla versions.
 
 =head1 ERRORS
 
-If a particular webservice call fails, it will throw a standard XML-RPC
-error. There will be a numeric error code, and then the description
-field will contain descriptive text of the error. Each error that Bugzilla
-can throw has a specific code that will not change between versions of
-Bugzilla.
+If a particular webservice call fails, it will throw an error in the
+appropriate format for the frontend that you are using. For all frontends,
+there is at least a numeric error code and descriptive text for the error.
 
 The various errors that functions can throw are specified by the
 documentation of those functions.
 
-If your code needs to know what error Bugzilla threw, use the numeric
-code. Don't try to parse the description, because that may change
-from version to version of Bugzilla.
+Each error that Bugzilla can throw has a specific numeric code that will
+not change between versions of Bugzilla. If your code needs to know what
+error Bugzilla threw, use the numeric code. Don't try to parse the
+description, because that may change from version to version of Bugzilla.
 
 Note that if you display the error to the user in an HTML program, make
 sure that you properly escape the error, as it will not be HTML-escaped.
@@ -259,21 +248,3 @@ would return something like:
   { users => [{ id => 1, real_name => 'John Smith' }] }
 
 =back
-
-
-=head1 EXTENSIONS TO THE XML-RPC STANDARD
-
-=head2 Undefined Values
-
-Normally, XML-RPC does not allow empty values for C<int>, C<double>, or
-C<dateTime.iso8601> fields. Bugzilla does--it treats empty values as
-C<undef> (called C<NULL> or C<None> in some programming languages).
-
-Bugzilla also accepts a type called C<< <nil> >>, which is always considered
-to be C<undef>, no matter what it contains.
-
-=begin private
-
-nil is implemented by XMLRPC::Lite, in XMLRPC::Deserializer::decode_value.
-
-=end private
index f76d800fa325374e97437334c2b9170f97b74560..8ebad41d1d4f6102f3bf634492662e35ab476cda 100755 (executable)
@@ -56,6 +56,11 @@ use constant FIELD_MAP => {
 
 use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component);
 
+use constant DATE_FIELDS => {
+    comments => ['new_since'],
+    search   => ['last_change_time', 'creation_time'],
+};
+
 ######################################################
 # Add aliases here for old method name compatibility #
 ######################################################
index 172d757efb42d22d1986891ed6c1ac45a37f0949..83b4197aa82b212b1d2b29fdfd32b2ddfb968cfb 100755 (executable)
@@ -133,5 +133,4 @@ sub WS_DISPATCH {
     return $dispatch;
 };
 
-
 1;
diff --git a/Bugzilla/WebService/README b/Bugzilla/WebService/README
new file mode 100644 (file)
index 0000000..bbe3209
--- /dev/null
@@ -0,0 +1,18 @@
+The class structure of these files is a little strange, and this README
+explains it.
+
+Our goal is to make JSON::RPC and XMLRPC::Lite both work with the same code.
+(That is, we want to have one WebService API, and have two frontends for it.)
+
+The problem is that these both pass different things for $self to WebService
+methods.
+
+When XMLRPC::Lite calls a method, $self is the name of the *class* the 
+method is in. For example, if we call Bugzilla.version(), the first argument
+is Bugzilla::WebService::Bugzilla. So in order to have $self
+(our first argument) act correctly in XML-RPC, we make all WebService
+classes use base qw(Bugzilla::WebService). 
+
+When JSON::RPC calls a method, $self is the JSON-RPC *server object*. In other
+words, it's an instance of Bugzilla::WebService::Server::JSONRPC. So we have
+Bugzilla::WebService::Server::JSONRPC inherit from Bugzilla::WebService.
diff --git a/Bugzilla/WebService/Server/JSONRPC.pm b/Bugzilla/WebService/Server/JSONRPC.pm
new file mode 100644 (file)
index 0000000..b453c61
--- /dev/null
@@ -0,0 +1,266 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (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.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla JSON Webservices Interface.
+#
+# The Initial Developer of the Original Code is the San Jose State
+# University Foundation. Portions created by the Initial Developer
+# are Copyright (C) 2008 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s): 
+#   Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::WebService::Server::JSONRPC;
+
+use strict;
+use base qw(JSON::RPC::Server::CGI Bugzilla::WebService::Server);
+
+use Bugzilla::Error;
+use Bugzilla::WebService::Constants;
+use Date::Parse;
+use DateTime;
+
+sub new {
+    my $class = shift;
+    my $self = $class->SUPER::new(@_);
+    Bugzilla->_json_server($self);
+    $self->dispatch(WS_DISPATCH);
+    $self->return_die_message(1);
+    $self->json->allow_blessed(1);
+    $self->json->convert_blessed(1);
+    # Default to JSON-RPC 1.0
+    $self->version(0);
+    return $self;
+}
+
+# Override the JSON::RPC method to return our CGI object instead of theirs.
+sub cgi { return Bugzilla->cgi; }
+
+sub type {
+    my ($self, $type, $value) = @_;
+    
+    # This is the only type that does something special with undef.
+    if ($type eq 'boolean') {
+        return $value ? JSON::true : JSON::false;
+    }
+    
+    return JSON::null if !defined $value;
+
+    my $retval = $value;
+
+    if ($type eq 'int') {
+        $retval = int($value);
+    }
+    if ($type eq 'double') {
+        $retval = 0.0 + $value;
+    }
+    elsif ($type eq 'string') {
+        # Forces string context, so that JSON will make it a string.
+        $retval = "$value";
+    }
+    elsif ($type eq 'dateTime') {
+        # str2time uses Time::Local internally, so I believe it should
+        # always return seconds based on the *Unix* epoch, even if the
+        # system doesn't use the Unix epoch.
+        $retval = str2time($value) * 1000;
+    }
+    # XXX Will have to implement base64 if Bugzilla starts using it.
+
+    return $retval;
+}
+
+##################
+# Login Handling #
+##################
+
+# This handles dispatching our calls to the appropriate class based on
+# the name of the method.
+sub _find_procedure {
+    my $self = shift;
+
+    # This is also a good place to deny GET requests, since we can
+    # safely call ThrowUserError at this point.
+    if ($self->request->method ne 'POST') {
+        ThrowUserError('json_rpc_post_only');
+    }
+
+    my $method = shift;
+    $self->{_bz_method_name} = $method;
+
+    # This tricks SUPER::_find_procedure into finding the right class.
+    $method =~ /^(\S+)\.(\S+)$/;
+    $self->path_info($1);
+    unshift(@_, $2);
+
+    return $self->SUPER::_find_procedure(@_);
+}
+
+# This is a hacky way to do something right before methods are called.
+# This is the last thing that JSON::RPC::Server::_handle calls right before
+# the method is actually called.
+sub _argument_type_check {
+    my $self = shift;
+    my $params = $self->SUPER::_argument_type_check(@_);
+
+    # This is the best time to do login checks.
+    $self->handle_login();
+
+    # If there are no parameters, we don't need to parse them.
+    return $params if !ref $params;
+
+    # JSON-RPC 1.0 requires all parameters to be passed as an array, so
+    # we just pull out the first item and assume it's an object.
+    if (ref $params eq 'ARRAY') {
+        $params = $params->[0];
+    }
+
+    # Now, convert dateTime fields on input.
+    $self->_bz_method_name =~ /^(\S+)\.(\S+)$/;
+    my ($class, $method) = ($1, $2);
+    my $pkg = $self->{dispatch_path}->{$class};
+    my @date_fields = @{ $pkg->DATE_FIELDS->{$method} || [] };
+    foreach my $field (@date_fields) {
+        if (defined $params->{$field}) {
+            my $value = $params->{$field};
+            if (ref $value eq 'ARRAY') {
+                $params->{$field} = 
+                    [ map { $self->_bz_convert_datetime($_) } @$value ];
+            }
+            else {
+                $params->{$field} = $self->_bz_convert_datetime($value);
+            }
+        }
+    }
+
+    # Bugzilla::WebService packages call internal methods like
+    # $self->_some_private_method. So we have to inherit from 
+    # that class as well as this Server class.
+    my $new_class = ref($self) . '::' . $pkg;
+    my $isa_string = 'our @ISA = qw(' . ref($self) . " $pkg)";
+    eval "package $new_class;$isa_string;";
+    bless $self, $new_class;
+
+    return $params;
+}
+
+sub _bz_convert_datetime {
+    my ($self, $time) = @_;
+    my $dt = DateTime->from_epoch(epoch => ($time / 1000), 
+                                  time_zone => Bugzilla->local_timezone);
+    return $dt->strftime('%Y-%m-%d %T');
+}
+
+sub handle_login {
+    my $self = shift;
+
+    my $path = $self->path_info;
+    my $class = $self->{dispatch_path}->{$path};
+    my $full_method = $self->_bz_method_name;
+    $full_method =~ /^\S+\.(\S+)/;
+    my $method = $1;
+    $self->SUPER::handle_login($class, $method, $full_method);
+}
+
+# _bz_method_name is stored by _find_procedure for later use.
+sub _bz_method_name {
+    return $_[0]->{_bz_method_name}; 
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::WebService::Server::JSONRPC - The JSON-RPC Interface to Bugzilla
+
+=head1 DESCRIPTION
+
+This documentation describes things about the Bugzilla WebService that
+are specific to JSON-RPC. For a general overview of the Bugzilla WebServices,
+see L<Bugzilla::WebService>.
+
+Please note that I<everything> about this JSON-RPC interface is
+B<UNSTABLE>. If you want a stable API, please use the
+C<XML-RPC|Bugzilla::WebService::Server::XMLRPC> interface.
+
+=head1 JSON-RPC
+
+Bugzilla supports both JSON-RPC 1.0 and 1.1. We recommend that you use
+JSON-RPC 1.0 instead of 1.1, though, because 1.1 is deprecated.
+
+At some point in the future, Bugzilla may also support JSON-RPC 2.0.
+
+The JSON-RPC standards are described at L<http://json-rpc.org/>.
+
+=head1 CONNECTING
+
+The endpoint for the JSON-RPC interface is the C<jsonrpc.cgi> script in
+your Bugzilla installation. For example, if your Bugzilla is at
+C<bugzilla.yourdomain.com>, then your JSON-RPC client would access the
+API via: C<http://bugzilla.yourdomain.com/jsonrpc.cgi>
+
+Bugzilla only allows JSON-RPC requests over C<POST>. C<GET> requests
+(or any other type of request, such as C<HEAD>) will be denied.
+
+=head1 PARAMETERS
+
+For JSON-RPC 1.0, the very first parameter should be an object containing
+the named parameters. For example, if you were passing two named parameters,
+one called C<foo> and the other called C<bar>, the C<params> element of
+your JSON-RPC call would look like:
+
+ "params": [{ "foo": 1, "bar": "something" }]
+
+For JSON-RPC 1.1, you can pass parameters either in the above fashion
+or using the standard named-parameters mechanism of JSON-RPC 1.1.
+
+C<dateTime> fields are represented as an integer number of microseconds
+since the Unix epoch (January 1, 1970 UTC). They are always in the UTC
+timezone.
+
+All other types are standard JSON types.
+
+=head1 ERRORS
+
+All errors thrown by Bugzilla itself have 100000 added to their numeric
+code. So, if the documentation says that an error is C<302>, then
+it will be C<100302> when it is thrown via JSON-RPC.
+
+Errors less than 100000 are errors thrown by the JSON-RPC library that
+Bugzilla uses, not by Bugzilla.
+
+=head1 SEE ALSO
+
+=head2 Server Types
+
+=over
+
+=item L<Bugzilla::WebService::Server::XMLRPC>
+
+=item L<Bugzilla::WebService::Server::JSONRPC>
+
+=back
+
+=head2 WebService Methods
+
+=over
+
+=item L<Bugzilla::WebService::Bug>
+
+=item L<Bugzilla::WebService::Bugzilla>
+
+=item L<Bugzilla::WebService::Product>
+
+=item L<Bugzilla::WebService::User>
+
+=back
index 36b4e01fd5c631414c2191bba7dad87d79d3fa11..5c92532a9e0ac9c63f0750fe19277c16dd880446 100644 (file)
@@ -168,3 +168,56 @@ sub as_string {
 }
 
 1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::WebService::Server::XMLRPC - The XML-RPC Interface to Bugzilla
+
+=head1 DESCRIPTION
+
+This documentation describes things about the Bugzilla WebService that
+are specific to XML-RPC. For a general overview of the Bugzilla WebServices,
+see L<Bugzilla::WebService>.
+
+=head1 XML-RPC
+
+The XML-RPC standard is described here: L<http://www.xmlrpc.com/spec>
+
+=head1 CONNECTING
+
+The endpoint for the XML-RPC interface is the C<xmlrpc.cgi> script in
+your Bugzilla installation. For example, if your Bugzilla is at
+C<bugzilla.yourdomain.com>, then your XML-RPC client would access the
+API via: C<http://bugzilla.yourdomain.com/xmlrpc.cgi>
+
+=head1 PARAMETERS
+
+C<dateTime> fields are the standard C<dateTime.iso8601> XML-RPC field. They
+should be in C<YYYY-MM-DDTHH:MM:SS> format (where C<T> is a literal T).
+
+All other fields are standard XML-RPC types.
+
+=head2 How XML-RPC WebService Methods Take Parameters
+
+All functions take a single argument, a C<< <struct> >> that contains all parameters.
+The names of the parameters listed in the API docs for each function are the
+C<< <name> >> element for the struct C<< <member> >>s.
+
+=head1 EXTENSIONS TO THE XML-RPC STANDARD
+
+=head2 Undefined Values
+
+Normally, XML-RPC does not allow empty values for C<int>, C<double>, or
+C<dateTime.iso8601> fields. Bugzilla does--it treats empty values as
+C<undef> (called C<NULL> or C<None> in some programming languages).
+
+Bugzilla also accepts a type called C<< <nil> >>, which is always considered
+to be C<undef>, no matter what it contains.
+
+=begin private
+
+nil is implemented by XMLRPC::Lite, in XMLRPC::Deserializer::decode_value.
+
+=end private
\ No newline at end of file
index 146867294ce1c629f63842dc5d70dc35d0cc42a4..754d49f703cc3711138f8680ae2f62d114b33cbf 100644 (file)
@@ -24,7 +24,7 @@ use warnings;
 use base qw(Bugzilla::WebService);
 use Bugzilla::Error;
 
-# This can be called as Example.hello() from XML-RPC.
+# This can be called as Example.hello() from the WebService.
 sub hello { return 'Hello!'; }
 
 sub throw_an_error { ThrowUserError('example_my_error') }
diff --git a/jsonrpc.cgi b/jsonrpc.cgi
new file mode 100644 (file)
index 0000000..c04f20f
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (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.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla JSON Webservices Interface.
+#
+# The Initial Developer of the Original Code is the San Jose State
+# University Foundation. Portions created by the Initial Developer 
+# are Copyright (C) 2008 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s): 
+#   Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::WebService::Constants;
+# This eval allows runtests to pass even if JSON::RPC isn't
+# installed.
+eval { require Bugzilla::WebService::Server::JSONRPC; };
+$@ && ThrowCodeError('json_rpc_not_installed');
+
+Bugzilla->usage_mode(USAGE_MODE_JSON);
+
+local @INC = (bz_locations()->{extensionsdir}, @INC);
+my $server = new Bugzilla::WebService::Server::JSONRPC;
+$server->dispatch(WS_DISPATCH)->handle();
index da8f902d47a2cdb436225d1e3cf07d7cecb7f852..9d182d25f1e9a09c066c52d3f8804735a948dae5 100644 (file)
     the job "[% job FILTER html %]".  You need to add this job type
     to the <code>JOB_MAP</code> constant in <code>Bugzilla::JobQueue</code>.
 
+  [% ELSIF error == "json_rpc_not_installed" %]
+   [% admindocslinks = { 'installation.html#install-perlmodules' 
+                         => 'Installing Perl modules' } %]
+    The JSON-RPC interface will not work without the <code>JSON::RPC</code>
+    Perl module being installed. Run <code>checksetup.pl</code> for
+    installation instructions. 
+
   [% ELSIF error == "ldap_bind_failed" %]
     Failed to bind to the LDAP server. The error message was: 
     <code>[% errstr FILTER html %]</code>
index 58be9ea73f8bb7a7d853df842c1ac07fa44fa419..338088178ee31871926163559a99826036b094b3 100644 (file)
     [% title = "Invalid Username Or Password" %]
     The username or password you entered is not valid.
 
+  [% ELSIF error == "json_rpc_post_only" %]
+    For security reasons, you may only use JSON-RPC with the POST
+    HTTP method.
+
   [% ELSIF error == "keyword_already_exists" %]
     [% title = "Keyword Already Exists" %]
     A keyword with the name [% name FILTER html %] already exists.
index 10c245ef611ad06e257e12e185788b092362a553..c98dd1b735e45d052be20597e6fd08726e0c0bbf 100755 (executable)
@@ -28,7 +28,7 @@ use Bugzilla::WebService::Constants;
 eval { require Bugzilla::WebService::Server::XMLRPC; };
 $@ && ThrowCodeError('soap_not_installed');
 
-Bugzilla->usage_mode(USAGE_MODE_WEBSERVICE);
+Bugzilla->usage_mode(USAGE_MODE_XMLRPC);
 
 # Fix the error code that SOAP::Lite uses for Perl errors.
 local $SOAP::Constants::FAULT_SERVER;