changeset 27048:159402e52cfa

New implementation of RESTful web services as part of GSoC 2018 (patch #9795). * scripts/web/weboptions.m: New function for RESTful web services. * scripts/web/webread.m: New function for RESTful web services. * scripts/web/webwrite.m: New function for RESTful web services. * scripts/web/module.mk: Add new functions to build system. * scripts/module.mk: Add new module to build system. * NEWS: Announce new feature. * libinterp/corefcn/urlwrite.cc (__restful_service__): New function. * liboctave/util/url-transfer[.cc/.h] (struct weboptions, cookie_jar, set_header_fields, form_data_post, set_weboptions, http_get, http_post, http_action): New functions to support the RESTful webservices. Refactor the functions http_get, http_post, http_action.
author Sahil Yadav <yadavsahil5198@gmail.com>
date Mon, 15 Apr 2019 14:03:58 +0900
parents 758063af3d0c
children eb522480d44c
files NEWS libinterp/corefcn/urlwrite.cc liboctave/util/url-transfer.cc liboctave/util/url-transfer.h scripts/module.mk scripts/web/module.mk scripts/web/weboptions.m scripts/web/webread.m scripts/web/webwrite.m
diffstat 9 files changed, 914 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Apr 12 13:15:34 2019 -0700
+++ b/NEWS	Mon Apr 15 14:03:58 2019 +0900
@@ -38,6 +38,11 @@
 
 ### Matlab compatibility
 
+- Complex RESTful web services can now be accessed by the `webread` and
+  `webwrite` functions alongside with the `weboptions` structure.  One major
+  feature is the support for cookies to enable RESTful communication with the
+  web service.
+
 - The interpreter now supports handles to nested functions.
 
 - The graphics properties `"LineWidth"` and `"MarkerSize"` are now
--- a/libinterp/corefcn/urlwrite.cc	Fri Apr 12 13:15:34 2019 -0700
+++ b/libinterp/corefcn/urlwrite.cc	Mon Apr 15 14:03:58 2019 +0900
@@ -47,6 +47,7 @@
 #include "oct-map.h"
 #include "oct-refcount.h"
 #include "ov-cell.h"
+#include "ov-classdef.h"
 #include "ovl.h"
 #include "pager.h"
 #include "unwind-prot.h"
@@ -680,7 +681,8 @@
   std::string target;
 
   if (nargin == 3 && ! args(2).isempty ())
-    target = args(2).xstring_value ("__ftp_mget__: TARGET must be a string") + octave::sys::file_ops::dir_sep_str ();
+    target = args(2).xstring_value ("__ftp_mget__: TARGET must be a string")
+                        + octave::sys::file_ops::dir_sep_str ();
 
   octave::url_handle_manager& uhm = interp.get_url_handle_manager ();
 
@@ -738,3 +740,93 @@
 
   return ovl ();
 }
+
+DEFUN (__restful_service__, args, nargout,
+       doc: /* -*- texinfo -*-
+@deftypefn  {} {@var{response} =} __restful_service__ (@var{url}, @var{param}, @var{weboptions})
+Undocumented internal function.
+@end deftypefn */)
+{
+  int nargin = args.length ();
+
+  if (nargin < 1)
+    print_usage ();
+
+  std::string url = args(0).xstring_value ("__restful_service__: URL must be a string");
+
+  std::ostringstream content;
+
+  octave::url_transfer url_xfer (url, content);
+
+  if (! url_xfer.is_valid ())
+    error ("support for URL transfers was disabled when Octave was built");
+
+  Array<std::string> param = args(1).cellstr_value ();
+
+  std::string data, method;
+
+  struct octave::weboptions options;
+
+  octave::cdef_object object = args (nargin - 1).classdef_object_value ()
+                                                         -> get_object ();
+
+  // We could've used object.map_value () instead to return a map but that
+  // shows a warning about about overriding access restrictions.
+  // Nevertheless, we are keeping checking that here if the keys are not
+  // equal to "delete" and "display", getting away with the warning.
+  string_vector keys = object.map_keys ();
+
+  for (int i = 0; i < keys.numel (); i++)
+  {
+    if (keys(i) == "Timeout")
+    {
+      float timeout = object.get (keys(i)).float_value ();
+      options.Timeout = static_cast<long>(timeout * 1000);
+    }
+
+    if (keys(i) == "HeaderFields")
+    {
+      options.HeaderFields = object.get (keys(i)).cellstr_value ();
+    }
+
+    // FIXME: 'delete' and 'display', auto-generated, probably by cdef_object
+    // class?  Remaining fields have already been adjusted elsewhere in the
+    // m-script.  Set 'value' as the Value of the Key wherever it's a string.
+    if (keys(i) != "Timeout" && keys(i) != "HeaderFields"
+        && keys(i) != "delete" && keys(i) != "display")
+    {
+      std::string value = object.get (keys(i)).string_value ();
+
+      if (keys(i) == "UserAgent")
+        options.UserAgent = value;
+
+      if (keys(i) == "Username")
+        options.Username = value;
+
+      if (keys(i) == "Password")
+        options.Password = value;
+
+      if (keys(i) == "ContentReader")
+        // Unimplemented. Only for MATLAB compatibility.
+        options.ContentReader = "";
+
+      if (keys(i) == "RequestMethod")
+        method = value;
+
+      if (keys(i) == "ArrayFormat")
+        options.ArrayFormat = value;
+
+      if (keys(i) == "CertificateFilename")
+        options.CertificateFilename = "";
+    }
+  }
+
+  url_xfer.set_weboptions (options);
+
+  url_xfer.http_action (param, method);
+
+  if (nargout < 2 && ! url_xfer.good ())
+    error ("__restful_service__: %s", url_xfer.lasterror ().c_str ());
+
+  return ovl (content.str ());
+}
--- a/liboctave/util/url-transfer.cc	Fri Apr 12 13:15:34 2019 -0700
+++ b/liboctave/util/url-transfer.cc	Mon Apr 15 14:03:58 2019 +0900
@@ -40,6 +40,7 @@
 #include "oct-env.h"
 #include "unwind-prot.h"
 #include "url-transfer.h"
+#include "version.h"
 
 #if defined (HAVE_CURL)
 #  include <curl/curl.h>
@@ -643,40 +644,154 @@
 
     void http_get (const Array<std::string>& param)
     {
-      url = host_or_url;
-
-      std::string query_string = form_query_string (param);
-
-      if (! query_string.empty ())
-        url += '?' + query_string;
-
-      SETOPT (CURLOPT_URL, url.c_str ());
-
-      perform ();
+      http_action (param, "get");
     }
 
     void http_post (const Array<std::string>& param)
     {
-      SETOPT (CURLOPT_URL, host_or_url.c_str ());
-
-      std::string query_string = form_query_string (param);
-
-      SETOPT (CURLOPT_POSTFIELDS, query_string.c_str ());
-
-      perform ();
+      http_action (param, "post");
     }
 
     void http_action (const Array<std::string>& param, const std::string& action)
     {
+      url = host_or_url;
+
+      std::string query_string;
+
+      query_string = form_query_string (param);
+
       if (action.empty () || action == "get")
-        http_get (param);
-      else if (action == "post")
-        http_post (param);
+        {
+          if (! query_string.empty ())
+            url += '?' + query_string;
+
+          SETOPT (CURLOPT_URL, url.c_str ());
+        }
+      else if (action == "post" || action == "put" || action == "delete")
+        {
+          SETOPT (CURLOPT_POSTFIELDS, query_string.c_str ());
+
+          if (action == "put")
+            {
+              SETOPT (CURLOPT_CUSTOMREQUEST, "PUT");
+            }
+
+          if (action == "delete")
+            {
+              SETOPT (CURLOPT_CUSTOMREQUEST, "DELETE");
+            }
+
+          SETOPT (CURLOPT_URL, url.c_str ());
+        }
       else
         {
           ok = false;
           errmsg = "curl_transfer: unknown http action";
         }
+
+      if (ok)
+        perform ();
+    }
+
+    void cookie_jar (const std::string& filename)
+    {
+      SETOPT (CURLOPT_COOKIEJAR, filename.c_str ());
+
+      SETOPT (CURLOPT_COOKIEFILE, filename.c_str ());
+    }
+
+    // Sets the header fields in a transfer. Input should be in the form
+    // of an array of strings with pairs of keys and values together
+    void set_header_fields (const Array<std::string>& param)
+    {
+      struct curl_slist *slist = nullptr;
+
+      unwind_protect frame;
+
+      frame.add_fcn (curl_slist_free_all, slist);
+
+      if (param.numel () >= 2)
+      {
+        for (int i = 0; i < param.numel (); i += 2)
+        {
+          std::string header = param(i) + ": " + param(i+1);
+
+          slist = curl_slist_append (slist, header.c_str ());
+        }
+
+        SETOPT (CURLOPT_HTTPHEADER, slist);
+      }
+    }
+
+    // Sets and sends the form data associated with a transfer.
+    // Input should be an array of strings with each pair of strings
+    // corresponding to the fieldname and it's value.
+    // To attach a file, you should use 'file' as the fieldname with the
+    // path of the file as its value.
+    void form_data_post (const Array<std::string>& param)
+    {
+      struct curl_httppost *post = NULL, *last = NULL;
+
+      SETOPT (CURLOPT_URL, host_or_url.c_str ());
+
+      unwind_protect frame;
+
+      frame.add_fcn (curl_formfree, post);
+
+      if (param.numel () >= 2)
+      {
+        for (int i = 0; i < param.numel (); i += 2)
+          {
+            std::string name = param(i);
+            std::string data = param(i+1);
+
+            if (name == "file")
+               curl_formadd(&post, &last, CURLFORM_COPYNAME, name.c_str (),
+                            CURLFORM_FILE, data.c_str (), CURLFORM_END);
+            else
+               curl_formadd(&post, &last, CURLFORM_COPYNAME, name.c_str (),
+                            CURLFORM_COPYCONTENTS, data.c_str (), CURLFORM_END);
+          }
+
+        SETOPT (CURLOPT_HTTPPOST, post);
+      }
+
+      perform ();
+    }
+
+    // Sets the various options specified by weboptions object.
+    void set_weboptions (const struct weboptions& options)
+    {
+      // Remove this after completing fixmes.
+      std::string temp = "";
+
+      set_header_fields (options.HeaderFields);
+
+      SETOPT (CURLOPT_TIMEOUT, options.Timeout);
+
+      if (! options.UserAgent.empty ())
+        SETOPT (CURLOPT_USERAGENT, options.UserAgent.c_str ());
+
+      if (! options.Username.empty ())
+      {
+        if (! options.Password.empty ())
+          SETOPT (CURLOPT_USERPWD, (options.Username + ":" + options.Password)
+                                                                   .c_str ());
+        else
+          SETOPT (CURLOPT_USERPWD, (options.Username + ":").c_str ());
+      }
+
+      // Unimplemented. Only for MATLAB complatiblity.
+      if (! options.ContentReader.empty ())
+        temp = options.ContentReader;
+
+      // Unimplemented. Only for MATLAB complatiblity.
+      if (! options.ArrayFormat.empty ())
+        temp = options.ArrayFormat;
+
+      // Unimplemented. Only for MATLAB complatiblity.
+      if (! options.CertificateFilename.empty ())
+        temp = options.CertificateFilename;
     }
 
   private:
@@ -736,6 +851,17 @@
       // instead.
       SETOPT (CURLOPT_FTP_USE_EPSV, false);
 
+      // Set the user agent for the curl request
+      // Needed by mediaWiki API.
+      curl_version_info_data * data = curl_version_info(CURLVERSION_NOW);
+      const char *lib_ver = data->version;
+      std::string user_agent =
+          "GNU Octave/" + std::string (OCTAVE_VERSION) +
+          " (https://www.gnu.org/software/octave/ ; help@octave.org) libcurl/"
+          + std::string (lib_ver);
+
+      SETOPT (CURLOPT_USERAGENT, user_agent.c_str ());
+
       SETOPT (CURLOPT_NOPROGRESS, true);
       SETOPT (CURLOPT_FAILONERROR, true);
 
@@ -747,25 +873,26 @@
     {
       std::ostringstream query;
 
-      for (int i = 0; i < param.numel (); i += 2)
-        {
-          std::string name = param(i);
-          std::string text = param(i+1);
+      if (param.numel () >= 2)
+        for (int i = 0; i < param.numel (); i += 2)
+          {
+            std::string name = param(i);
+            std::string text = param(i+1);
 
-          // Encode strings.
-          char *enc_name = curl_easy_escape (curl, name.c_str (),
-                                             name.length ());
-          char *enc_text = curl_easy_escape (curl, text.c_str (),
-                                             text.length ());
+            // Encode strings.
+            char *enc_name = curl_easy_escape (curl, name.c_str (),
+                                               name.length ());
+            char *enc_text = curl_easy_escape (curl, text.c_str (),
+                                               text.length ());
 
-          query << enc_name << '=' << enc_text;
+            query << enc_name << '=' << enc_text;
 
-          curl_free (enc_name);
-          curl_free (enc_text);
+            curl_free (enc_name);
+            curl_free (enc_text);
 
-          if (i < param.numel ()-1)
-            query << '&';
-        }
+            if (i < param.numel ()-2)
+              query << '&';
+          }
 
       query.flush ();
 
--- a/liboctave/util/url-transfer.h	Fri Apr 12 13:15:34 2019 -0700
+++ b/liboctave/util/url-transfer.h	Mon Apr 15 14:03:58 2019 +0900
@@ -41,6 +41,19 @@
 
 namespace octave
 {
+  struct weboptions
+  {
+    std::string UserAgent;
+    long Timeout;
+    std::string Username;
+    std::string Password;
+    Array<std::string> HeaderFields;
+    std::string ContentReader;
+    std::string RequestMethod;
+    std::string ArrayFormat;
+    std::string CertificateFilename;
+  };
+
   class
   OCTAVE_API
   base_url_transfer
@@ -142,6 +155,13 @@
     virtual void http_action (const Array<std::string>& /* param */,
                               const std::string& /* action */) { }
 
+    virtual void cookie_jar (const std::string& /* filename */) { }
+
+    virtual void set_header_fields (const Array<std::string>& /* param */) { }
+
+    virtual void form_data_post (const Array<std::string>& /* param */) { }
+
+    virtual void set_weboptions (const struct weboptions& /* param */) { }
   protected:
 
     // Host for ftp transfers or full URL for http requests.
@@ -255,6 +275,25 @@
       rep->http_action (param, action);
     }
 
+    void cookie_jar (const std::string& filename)
+    {
+      rep->cookie_jar (filename);
+    }
+
+    void set_header_fields (const Array<std::string>& param)
+    {
+      rep->set_header_fields (param);
+    }
+
+    void form_data_post (const Array<std::string>& param)
+    {
+      rep->form_data_post (param);
+    }
+
+    void set_weboptions (const struct weboptions& param)
+    {
+      rep->set_weboptions (param);
+    }
   private:
 
     std::shared_ptr<base_url_transfer> rep;
--- a/scripts/module.mk	Fri Apr 12 13:15:34 2019 -0700
+++ b/scripts/module.mk	Mon Apr 15 14:03:58 2019 +0900
@@ -39,6 +39,7 @@
 include %reldir%/strings/module.mk
 include %reldir%/testfun/module.mk
 include %reldir%/time/module.mk
+include %reldir%/web/module.mk
 
 ## include %reldir%/@ftp/module.mk
 ## The include above fails because Automake cannot process the '@' character.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/web/module.mk	Mon Apr 15 14:03:58 2019 +0900
@@ -0,0 +1,16 @@
+FCN_FILE_DIRS += scripts/web
+
+%canon_reldir%_FCN_FILES = \
+  %reldir%/weboptions.m \
+  %reldir%/webread.m \
+  %reldir%/webwrite.m
+
+%canon_reldir%dir = $(fcnfiledir)/web
+
+%canon_reldir%_DATA = $(%canon_reldir%_FCN_FILES)
+
+FCN_FILES += $(%canon_reldir%_FCN_FILES)
+
+PKG_ADD_FILES += %reldir%/PKG_ADD
+
+DIRSTAMP_FILES += %reldir%/$(octave_dirstamp)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/web/weboptions.m	Mon Apr 15 14:03:58 2019 +0900
@@ -0,0 +1,386 @@
+## Copyright (C) 2018-2019 Sahil Yadav <yadavsahil5198@gmail.com>
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} {@var{output} =} weboptions ()
+## @deftypefnx {} {@var{output} =} weboptions (@var{name1}, @var{value1},...)
+##
+## Specify parameters for RESTful web services.
+##
+## weboptions returns a default weboptions object to specify parameters for a
+## request to a web service.  A weboptions object can be an optional input
+## argument to the webread, websave, and webwrite functions.
+##
+## You can specify several name and value pair arguments in any order as
+## @var{name1}, @var{value1}, @var{name2}, @var{value2}, etc.
+##
+## The names should be EXACTLY the same as specified in the help text.
+##
+## The following options are available:
+## @itemize @bullet
+## @item
+## @samp{CharacterEncoding} --- Specify the character encoding of the data:
+##
+## @samp{auto} (default), @samp{UTF-8}, @samp{US-ASCII}
+## @samp{auto} takes the value by determining the content-type of the data.
+##
+## @item
+## @samp{UserAgent} --- Specify the User agent for the connection.
+##
+## Default value is @samp{GNU Octave/version}, where @samp{version} is the
+## current Octave's version of user.
+##
+## @item
+## @samp{Timeout} --- Specify the timeout value for the connection in seconds.
+##
+## Default is 10 seconds.  @samp{Inf} is not supported currently.
+##
+## @item
+## @samp{Username} --- User identifier for a basic HTTP connection.
+##
+## Default is NULL.  It must be a string
+##
+## @item
+## @samp{Password} --- User authentication password for HTTP connection.
+##
+## Default is NULL. It must be a string or character vector.
+## Programming Note: If you display a weboptions object with the Password
+## property set, the value is displayed as a character vector containing ‘*’.
+## However, the object stores the value of the Password property as plain text.
+##
+## @item
+## @samp{KeyName} --- Specify the name of an additional key to be added to
+## the HTTP request header.  It should be coupled with @samp{KeyValue}.  It
+## must be a string or character vector.
+##
+## @item
+## @samp{KeyValue} --- Specify the value of the key @samp{KeyName}.
+##
+## @samp{KeyName} must be present in order to assign this field.
+##
+## @item
+## @samp{HeaderFields} --- Specify the header fields for the connection.
+##
+## Names and values of header fields, specified as an m-by-2 array of strings
+## or cell array of character vectors to add to the HTTP request header.
+## HeaderFields@{i,1@} is the name of a field and HeaderFields@{i,2@} is its
+## value.
+## @example
+## @group
+## weboptions ("HeaderFields", @{"Content-Length" "78";"Content-Type" "application/json"@})
+## creates a weboptions object that contains two header fields: Content-Length
+## with value 78 and Content-Type with value application/json.
+## @end group
+## @end example
+##
+## @item
+## @samp{ContentType} --- Specify the content type of the data.
+##
+## The following values are available:
+## @samp{auto}, @samp{text}, @samp{json}
+##
+## Default is @samp{auto}.  It automatically determines the content type.
+## All other formats like @samp{audio}, @samp{binary}, etc. available in
+## @sc{matlab} are not yet supported.
+##
+## @item
+## @samp{ContentReader} --- Not yet implemented.  Only for @sc{matlab}
+## compatibility.
+##
+## @item
+## @samp{MediaType} --- Not yet implemented.  Only for @sc{matlab}
+## compatibility.
+##
+## @item
+## @samp{RequestMethod} --- Specifies the type of request to be made.
+##
+## The following methods are available:
+## @samp{get}, @samp{put}, @samp{post}, @samp{delete}, @samp{patch}
+##
+## webread and websave use the HTTP GET method.  webwrite uses the HTTP POST
+## method as default.
+##
+## @item
+## @samp{ArrayFormat} -- Not yet implemented.  Only for @sc{matlab}
+## compatibility.
+##
+## @item
+## @samp{CertificateFilename} --- Not yet implemented. Only for @sc{matlab}
+## compatibility.
+## @end itemize
+##
+## @seealso{webwrite, webread, websave}
+## @end deftypefn
+
+classdef weboptions < handle
+  properties
+    CharacterEncoding = "auto";
+    UserAgent = ["GNU Octave/", version];
+    Timeout = 10;
+    Username = "";
+    Password = "";
+    KeyName = "";
+    KeyValue = "";
+    HeaderFields = {};
+    ContentType = "auto";
+    ContentReader = "";
+    MediaType = "auto";
+    RequestMethod = "auto";
+    ArrayFormat = "csv";
+    CertificateFilename = "";
+  endproperties
+
+  methods
+    function f = weboptions (varargin)
+      if (rem (numel (varargin), 2) != 0)
+        error ("weboptions: Invalid number of arguments");
+      else
+        h = cell2struct (varargin(2:2:end), varargin(1:2:end), 2);
+        if (numfields (h) > 14)
+          error ("weboptions: Invalid number of arguments");
+        endif
+
+        if (isfield (h, "CharacterEncoding"))
+          f.CharacterEncoding = h.CharacterEncoding;
+          h = rmfield (h, "CharacterEncoding");
+        endif
+
+        if (isfield (h, "UserAgent"))
+          f.UserAgent = h.UserAgent;
+          h = rmfield (h, "UserAgent");
+        endif
+
+        if (isfield (h, "Timeout"))
+          f.Timeout = h.Timeout;
+          h = rmfield (h, "Timeout");
+        endif
+
+        if (isfield (h, "Username"))
+          f.Username = h.Username;
+          h = rmfield (h, "Username");
+        endif
+
+        if (isfield (h, "Password"))
+          f.Password = h.Password;
+          h = rmfield (h, "Password");
+        endif
+
+        if (isfield (h, "KeyName"))
+          f.KeyName = h.KeyName;
+          h = rmfield (h, "KeyName");
+        endif
+
+        if (isfield (h, "KeyValue"))
+          f.KeyValue = h.KeyValue;
+          h = rmfield (h, "KeyValue");
+        endif
+
+        if (isfield (h, "HeaderFields"))
+          f.HeaderFields = h.HeaderFields;
+          h = rmfield (h, "HeaderFields");
+        endif
+
+        if (isfield (h, "ContentType"))
+          f.ContentType = h.ContentType;
+          h = rmfield (h, "ContentType");
+        endif
+
+        if (isfield (h, "ContentReader"))
+          f.ContentReader = h.ContentReader;
+          h = rmfield (h, "ContentReader");
+        endif
+
+        if (isfield (h, "MediaType"))
+          f.MediaType = h.MediaType;
+          h = rmfield (h, "MediaType");
+        endif
+
+        if (isfield (h, "RequestMethod"))
+          f.RequestMethod = h.RequestMethod;
+          h = rmfield (h, "RequestMethod");
+        endif
+
+        if (isfield (h, "ArrayFormat"))
+          f.ArrayFormat = h.ArrayFormat;
+          h = rmfield (h, "ArrayFormat");
+        endif
+
+        if (isfield (h, "CertificateFilename"))
+          f.CertificateFilename = h.CertificateFilename;
+          h = rmfield (h, "CertificateFilename");
+        endif
+
+        if (! isempty (fieldnames (h)))
+          field = fieldnames (h){1};
+          error (["weboptions: Undefined field " field "."]);
+        endif
+      endif
+    endfunction
+
+    function f = set.CharacterEncoding (f, value)
+      if (! any (strcmpi (value, {"UTF-8", 'US-ASCII', "auto"})));
+        error ("weboptions: Invalid CharacterEncoding value");
+      else
+        f.CharacterEncoding = value;
+      endif
+    endfunction
+
+    function f = set.UserAgent (f, value)
+      if (! ischar (value) && ! isvector (value));
+        error ("weboptions: UserAgent must be a String");
+      else
+        f.UserAgent = value;
+      endif
+    endfunction
+
+    function f = set.Timeout (f, value)
+      if (! isreal (value) || ! isscalar (value)
+          || floor(value) != value || value < 0)
+        error ("weboptions: Invalid Timeout value");
+      else
+        f.Timeout = value;
+      endif
+    endfunction
+
+    function f = set.Username (f, value)
+      if (! ischar (value) && ! isvector (value))
+        error ("weboptions: Username must be a string");
+      else
+        f.Username = value;
+      endif
+    endfunction
+
+    function f = set.Password (f, value)
+      if (! ischar (value) && ! isvector (value))
+        error ("weboptions: Password must be a string");
+      else
+        f.Password = value;
+      endif
+    endfunction
+
+    function f = set.KeyName (f, value)
+      if (! ischar (value) && ! isvector (value))
+        error ("weboptions: Invalid KeyName value");
+      else
+        f.KeyName = value;
+      endif
+    endfunction
+
+    function f = set.KeyValue (f, value)
+      if (isempty (f.KeyName) && ! isempty (value))
+        error ("weboptions: Field KeyName empty. Cannot set KeyValue.");
+      else
+        f.KeyValue = value;
+      endif
+    endfunction
+
+    function f = set.HeaderFields (f, value)
+      if (! isempty (value))
+        if (! iscellstr (value) || ! ismatrix (value))
+          error ("weboptions: HeaderFields must be array of strings or a cell array");
+        elseif (size (value)(2) != 2)
+          error ("weboptions: HeaderFields must be of size m-by-2");
+        endif
+      endif
+      f.HeaderFields = value;
+    endfunction
+
+    function f = set.ContentType (f, value)
+      if (! isempty (value))
+        if (! any (strcmpi (value, {"json", "text", "auto"})))
+          error ("weboptions: Invalid ContentType value");
+        endif
+      endif
+      f.ContentType = value;
+    endfunction
+
+    function f = set.ContentReader (f, value)
+      f.ContentReader = value;
+    endfunction
+
+    function f = set.MediaType (f, value)
+      f.MediaType = value;
+    endfunction
+
+    function f = set.RequestMethod (f, value)
+      if (! isempty (value))
+        if (! any (strcmpi (value, {"auto", "get", "put", "post",...
+                                    "delete", "patch"})))
+          error ("weboptions: Invalid RequestMethod value");
+        endif
+      endif
+      f.RequestMethod = value;
+    endfunction
+
+    function f = set.ArrayFormat (f, value)
+      if (! isempty (value))
+        if (! any (strcmpi (value, {"csv", "json",...
+                                    "repeating", "php"})))
+          error ("weboptions: Invalid ArrayFormat value");
+        endif
+      endif
+      f.ArrayFormat = value;
+    endfunction
+
+    function f = set.CertificateFilename (f, value)
+      f.CertificateFilename = value;
+    endfunction
+
+    function display (f)
+      Timeout = int2str (f.Timeout);
+      Password = repmat ("*", 1, numel (num2str (f.Password)));
+
+      if (! isempty (f.ContentReader))
+        f.ContentReader
+        ContentReader = ['["', strjoin(f.ContentReader, '", "'), '"]'];
+      else
+        ContentReader = "[]";
+      endif
+
+      if (! isempty (f.HeaderFields))
+        HeaderFields = ['{"', strjoin(f.HeaderFields, '", "'), '"}'];
+      else
+        HeaderFields = "{}";
+      endif
+
+      if (! isempty (f.KeyValue))
+        KeyValue = num2str (f.KeyValue);
+      else
+        KeyValue = "\'\'";
+      endif
+
+      printf ("%s =", inputname (1));
+      output = ["\n\n   weboptions with properties:     \n",...
+                "\n      CharacterEncoding: \'", f.CharacterEncoding, "\'",...
+                "\n              UserAgent: \'", f.UserAgent, "\'",...
+                "\n                Timeout: "  , Timeout, "",...
+                "\n               Username: \'", f.Username, "\'",...
+                "\n               Password: \'", Password, "\'",...
+                "\n                KeyName: \'", f.KeyName, "\'",...
+                "\n               KeyValue: "  , KeyValue,...
+                "\n            ContentType: \'", f.ContentType, "\'",...
+                "\n          ContentReader: "  , ContentReader,...
+                "\n              MediaType: \'", f.MediaType, "\'",...
+                "\n          RequestMethod: \'", f.RequestMethod, "\'",...
+                "\n            ArrayFormat: \'", f.ArrayFormat, "\'",...
+                "\n           HeaderFields: "  , HeaderFields,...
+                "\n    CertificateFilename: \'", f.CertificateFilename, "\'\n"];
+      disp (output);
+    endfunction
+  endmethods
+endclassdef
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/web/webread.m	Mon Apr 15 14:03:58 2019 +0900
@@ -0,0 +1,104 @@
+## Copyright (C) 2018-2019 Sahil Yadav <yadavsahil5198@gmail.com>
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} {@var{response} =} webread (@var{url})
+## @deftypefnx {} {@var{response} =} webread (@var{url}, @var{name1}, @var{value1},...)
+## @deftypefnx {} {@var{response} =} webread (..., @var{options})
+##
+## Read content from RESTful web service.
+##
+## Reads content from the web service specified by @var{url} and returns the
+## content in @var{response}.
+##
+## If the pairs @var{name1}, @var{value1}... are given, append these key-value
+## pairs of query parameters to @var{url}.  To put a query into the body of the
+## message, use @code{webwrite}.  The web service defines the query parameters.
+##
+## @var{options} is a @code{weboptions} object to be used to add other HTTP
+## request options.  You can use this option with any of the input arguments of
+## the previous syntax.
+##
+## See help text for @code{weboptions} to see the complete list of supported
+## HTTP operations.
+##
+## @seealso{weboptions, webwrite, websave}
+## @end deftypefn
+
+function response = webread (url, varargin)
+
+  if (nargin < 1)
+    print_usage();
+  endif
+
+  if (! (ischar (url) && isvector (url)))
+    error ("webread: URL must be a string");
+  endif
+
+  has_weboptions = false;
+  options = weboptions;
+
+  if (numel (varargin) > 0)
+    if (isa (varargin{end}, "weboptions"))
+      has_weboptions = true;
+      options = varargin{end};
+    endif
+  endif
+
+  if (strcmp (options.MediaType, "auto"))
+    options.MediaType = "application/x-www-form-urlencoded";
+  endif
+
+  ## If MediaType is set by the user, append it to other headers.
+  if (! strcmp (options.CharacterEncoding, "auto"))
+    options.HeaderFields{end+1,1} = "Content-Type";
+    options.HeaderFields{end,2} = [options.MediaType,...
+                                  "; charset=", options.CharacterEncoding];
+  endif
+
+  if (! isempty (options.KeyName))
+    options.HeaderFields{end+1,1} = options.KeyName;
+    options.HeaderFields{end,2} = options.KeyValue;
+  endif
+
+  if (strcmp (options.RequestMethod, "auto"))
+    options.RequestMethod = "get";
+  endif
+
+  ## Flatten the cell array because the internal processing takes place on
+  ## a flattened array.
+  options.HeaderFields = options.HeaderFields(:)';
+
+  if (nargin == 1 || (nargin == 2 && has_weboptions))
+    response = __restful_service__ (url, cell, options);
+  elseif (rem (numel (varargin), 2) == 1 && has_weboptions)
+    if (iscellstr (varargin(1:end-1)))
+      response = __restful_service__ (url, varargin(1:end-1), options);
+    else
+      error ("webread: Keys and Values must be string.");
+    endif
+  elseif (rem (numel (varargin), 2) == 0 && ! has_weboptions)
+    if (iscellstr (varargin))
+      response = __restful_service__ (url, varargin, options);
+    else
+      error ("webread: Keys and Values must be string.");
+    endif
+  else
+    error ("webread: Wrong input arguments");
+  endif
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/web/webwrite.m	Mon Apr 15 14:03:58 2019 +0900
@@ -0,0 +1,108 @@
+## Copyright (C) 2018-2019 Sahil Yadav <yadavsahil5198@gmail.com>
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} {@var{response} =} webwrite (@var{url}, @var{name1}, @var{value1},...)
+## @deftypefnx {} {@var{response} =} webwrite (@var{url}, @var{data})
+## @deftypefnx {} {@var{response} =} webwrite (..., @var{options})
+##
+## Write data to RESTful web services.
+##
+## Writes content to web service specified by @var{url} and returns the
+## response in @var{response}.
+##
+## All pairs @var{name1}, @var{value1}... are given, adds these key-value
+## pairs of query parameters to the body of request method (@code{get},
+## @code{post}, @code{put}, etc).
+##
+## @var{options} is a @code{weboptions} object to be used to add other HTTP
+## request options.  You can use this option with any of the input arguments of
+## the previous syntax.
+##
+## See @code{help weboptions} for a complete list of supported HTTP options.
+##
+## @seealso{weboptions, webread, websave}
+## @end deftypefn
+
+function response = webwrite (url, varargin)
+
+  if (numel (varargin) < 1)
+    print_usage();
+  endif
+
+  if (! (ischar (url) && isvector (url)))
+    error ("webwrite: URL must be a string");
+  endif
+
+  options = weboptions;
+  has_weboptions = false;
+
+  if (isa (varargin{end}, "weboptions"))
+    has_weboptions = true;
+    options = varargin{end};
+  endif
+
+  if (strcmp (options.MediaType, "auto"))
+    options.MediaType = "application/x-www-form-urlencoded";
+  endif
+
+  ## If MediaType is set by the user, append it to other headers.
+  if (! strcmp (options.CharacterEncoding, "auto"))
+    options.HeaderFields{end+1, 1} = "Content-Type";
+    options.HeaderFields{end, 2} = [options.MediaType,...
+                                  "; charset=", options.CharacterEncoding];
+  endif
+
+  if (! isempty (options.KeyName))
+    options.HeaderFields{end+1, 1} = options.KeyName;
+    options.HeaderFields{end, 2} = options.KeyValue;
+  endif
+
+  if (strcmp (options.RequestMethod, "auto"))
+    options.RequestMethod = "post";
+  endif
+
+  ## Flatten the cell array because the internal processing takes place on
+  ## a flattened array.
+  options.HeaderFields = options.HeaderFields(:)';
+
+  if (numel (varargin) == 2)
+    if ((ischar (varargin{1}) && isvector (varargin{1})) && has_weboptions)
+      param = strsplit (varargin{1}, {"=", "&"});
+      response = __restful_service__ (url, param, options);
+    elseif (! has_weboptions && iscellstr (varargin))
+      response = __restful_service__ (url, varargin, options);
+    else
+      error ("webwrite: data should be a character array or string.");
+    endif
+  elseif (rem (numel (varargin), 2) == 1 && has_weboptions)
+    if (iscellstr (varargin(1:end-1)))
+      response = __restful_service__ (url, varargin(1:end-1), options);
+    else
+      error ("webwrite: Keys and Values must be string.");
+    endif
+  elseif (rem (numel (varargin), 2) == 0 && ! has_weboptions)
+    if (iscellstr (varargin))
+      response = __restful_service__ (url, varargin, options);
+    else
+      error ("webwrite: Keys and Values must be string.");
+    endif
+  else
+    error ("webwrite: Wrong input arguments");
+  endif
+endfunction