Troubleshooting web services

Troubleshooting web services

by Nik Okuntseff -
Number of replies: 6

It took me quite a while to figure our how to troubleshoot web services. I hope this summary will help someone to fix their web service related issues. Sorry for a long post.

My project was: investigate how to create Moodle users and enroll them in courses programmatically via its web services API. This procedure describes how to configure Moodle to use web services API and troubleshooting techniques I used to resolve problems.

ENVIRONMENT

Moodle 2.4.3, running on Windows 7 with PHP 5.4.11, MySQL 5.5.27.

PRELIMINARY NOTES

For me, there were many steps to get through before things started to work. Also, from my experience, Moodle web-service related stuff tends to change from version to version. For example: methods get changed and it may be difficult to construct function parameters correctly. What it means is that the code that works now will break when things change. You may use the troubleshooting techniques described below to adjust things when necessary.

CONFIGURING MOODLE

* Installed Moodle 2.4.3 using php 5.4.11. Installation process:
** Download and deploy code on web server.
** Create database, grant access permissions to the database.
** Access the site via web browser and follow a lengthy process of Moodle install.
* Configuring Web Services.
** Go to Home - Site administration - Advanced features. Mark the "Enable web services" option. Click "Save changes" on the bottom.
** Go to Home - Site administration - Plugins - Web services - Manage protocols. Enable the XML-RPC protocol (by clicking on the marker in the "Enable" column). Click "Save changes" on the bottom.
* Create a web service.
** Go to Home - Site administration - Plugins - Web services - External services. Click "Add". Specify a name (for example, nik_test) and mark the "Enabled" box.
** Add functions to just created web service. Go to Home - Site administration - Plugins - Web services - External services - Functions. Click "Add functions". I needed to add the following functions:
*** core_user_get_users_by_id - to get information about users.
*** core_user_create_users - to create users.
*** core_user_update_users - to update users.
*** core_user_delete_users - to update users.
*** core_course_get_courses - to get information about courses.
*** enrol_manual_enrol_users - to enroll users in courses.
* Create a user to access web services by going to Home - Site administration - Users - Accounts - Browse list of users. Click the "Add a new user" link. Create a user, for example, web_service_user with all default other values.
* Go to Home - Site Administration - Users - Permissions - Define roles and create a new role, for example "Web Services Users".
* Now we need to assign capabilities to this role. It is critical for this role to have all the required capabilities in order to execute the functions above. When a capability is missing, Moodle generates and catches exceptions internally, leaving you in the dark about what may be wrong, see debugging section below.  Here is a list of capabilities we need:
** webservice/xmlrpc:use - or "Use XML-RPC protocol" - required to process XML-RPC communications.
** moodle/user:viewdetails - or "View user profiles" - required to view user info.
** moodle/user:viewhiddendetails - or "View hidden details of users" - required to view user info.
** moodle/course:useremail - or "Enable/disable email address" - required to view user info.
** moodle/user:create - or "Create users" - required to create user accounts.
** moodle/user:delete - or "Delete users" - required to delete user accounts.
** moodle/user:update - or "Update user profiles" - required to update user accounts.
** moodle/course:view - or "View courses without participation" - required to get course info.
** moodle/course:viewhiddencourses - or "View hidden courses" - required to get course info.
** moodle/course:update - or "Update course settings" - required to get course info.
** enrol/manual:enrol - or "Enrol users" - required to enroll users in courses.
* There is an additional step that is required for enroll function to work. We need to allow "Web Services Users" to assign a "Student" role to accounts. This is done on the "Allow role assignments" tab in Site administration - Users - Permissions - Define Roles. Make sure that the "Student" checkbox is checked for "Web Services Users" role. By now we defined a system role that is capable to execute our functions.
* Assign web_service_user account to the "Web Services Users" role by going to Home - Site administration - Users - Permissions - Assign system roles. Click on the "Web Services Users" and add the web_service_user to the role on the next page. This makes web_service_user to have the capabilities assigned to the "Web Services Users" role in the previous steps. By now we have a Moodle account with the capabilities necessary to execute our functions.
* Create a token to access web service (we'll need it in code examples). Go to Home - Site administration - Plugins - Web services - Manage tokens. Click "Add". Select a user and a service and generate token.

SUMMARY

By this point we have:
* Installed Moodle 2.4.3.
* Enabled web services in it.
* Created nik_test web service with a few functions.
* Created web_service_user user account.
* Created "Web Services Users" system role with necessary capabilities to execute our functions and also a capability to assign the "Student" role to other accounts.
* Created a token for web_service_user account for programmatic access to nik_test service.

DEBUGGING AND TROUBLESHOOTING

As you can see from the above lists, the setup procedure is quite demanding. Numerous points of failure are possible, here are just a few possibilities.
* Moodle not set up correctly.
* Moodle expects parameters in a certain way, documentation conflicting, incorrect, missing, or deprecated.
* Coding errors.
* Component errors. For example, PEAR XML_RPC package that I used in earlier samples could not decode some replies from Moodle, such as containing <value><nil/></value> elements.
* Moodle returns a call that contains nothing and it is unclear what's wrong.
Here are two troubleshooting ideas that worked for me.
* Output additional info to web server log from the modified Moodle code. This was the primary thing.
* Had a special tool to observe incoming XML that hits the server.
I tried to work like this:
* Understand the XML format of the request that I need to send to Moodle.
* Create such request and observe it somewhere to make sure the format is right. I did it by writing a simple Java servlet running under Tomcat that was dumping the POST body to its console. Then configured my client to talk to it, and looked at XML. When you see XML everything becomes so much easier. However, if you use xmlrpc_encode_request as I do, you can also see the XML right in PHP client by doing var_dump:
<pre>
// Create XML for Moodle.
$request = xmlrpc_encode_request('core_user_get_users_by_id', array(array((string) $user_id)), array('encoding'=>'UTF-8'));
var_dump($request);  // In case you want to see XML.
</pre>

* When we are sure the request is correct we fire it at Moodle and see what happens. At this point, in case of problems, we need to examine web server error log. It will help if you start with a clean error log before making a call. From that point on, we determine where in Moodle the problem occurs, and insert additional debug output in Moodle code around the place to localize the issue precisely.
* You can dump simple values and strings using the error_log() function but dumping arrays does not work, until you know this trick, which works a bit better:
<pre>
ob_start();
print_r($sigParams);
$contents = ob_get_contents();
ob_end_clean();
error_log($contents);
</pre>

Here are three problems that were most challenging for me in the scope of this project, and how I solved them.

 

1) My previous code samples used PEAR XML_RPC package to do XML-RPC communications. Things kind of worked but not always. In the end and after a long struggle, I dumped the content of replies that Moodle sends back to error.log and saw this:

<pre>
<?xml version="1.0" encoding="UTF-8"?>\n
<methodResponse>
  <params>
    <param>
      <value><nil/></value>
    </param>
  </params>
</methodResponse>
</pre>
See the <nil/> inside the <value> element. I turned out that the XML_RPC package on the client could not decode such elements. The solution was to abandon XML_RPC package and use the experimental xmlrpc functions that are a part of PHP.

 

2) The second, and probably the most important problem, was to figure out how to construct parameters in XML for each function call. They need to be built in a certain fashion. For me, the difficult function was enrol_manual_enrol_users, as I could not figure it out, and the docs as well as code seemed conflicting to me. So, I narrowed the problem down to a place where Moodle compares a signature for a function with the actual structure received. Dumped the (beginnings of...) signatures and saw some differences. Finally, I figured out that the parameters need to be like:
<pre>
$params = array(array(array('roleid'=>'5', 'userid'=>'42', 'courseid'=>'2')));
$request = xmlrpc_encode_request('enrol_manual_enrol_users', $params, array('encoding'=>'UTF-8'));
var_dump($request);  // In case you want to see XML.
</pre>

 

3) Missing capabilities for the web service account. When this happens, Moodle generates exceptions. You can localize them in web server logs and then use the technique above to find out the exact reason. Well, there is a thing that may save you a lot of time: in Moodle look at the  Home - Site administration - Plugins - Web services - Manage tokens page. If any capabilities are missing for your functions you will see them there.

CODE SAMPLES WITH PEAR XML_RPC PACKAGE

My earlier code samples for Moodle API used PEAR XML_RPC package (version 1.5.3). They are not very much useful because of the decoding problem for elements like <value><nil/></value> that Moodle likes to send back sometimes.

CODE SAMPLE WITH MOODLE CLASS

I wrote a class called Moodle that you can instantiate and call. Something like this:

$token = 'cface2c6d6e7888f5e33485008dbda88';
$server = 'url_to_server:port';
$dir = '/moodle'; // May be null if moodle runs in the root directory in the server.

// To do things with Moodle, we create a new Moodle class, initialize it, and then call its functions.
$moodle = new Moodle();

// Initialize the class.
$fields = array('token'=>$token, 'server'=>$server, 'dir'=>$dir);
$moodle->init($fields);

// Usage examples.
// Normally, a function returns something useful, such as an array of user properties, or TRUE, on success.
// When something happens the return is FALSE, and the last error string is in $moodle->error string.
// A lot of things need to be done in Moodle for web services API to work properly.

// Get user information.
$id = 1; // User id in Moodle. 1 for guest user.
$user = $moodle->getUser($id);
if ($user)
  var_dump($user);          // Success, normal result.
else
  var_dump($moodle->error); // Error.


The following functions are supported by the class at the moment:
* getUser($user_id)
* createUser($fields)
* updateUser($fields)
* deleteUser($user_id)
* getCourse($course_id)
* enrollUser($user_id, $course_id)

They all work in a similar fashion: they return something useful such as the result of the requested operation. If an error occurs, the return is FALSE, and you can look at the $error member for information about the last error.

Cheers!

TO BE CONTINUED...  If needed, I will post the code for the class.

Average of ratings: -
In reply to Nik Okuntseff

Re: Troubleshooting web services

by amit gupta -

Hey Nik,

how do i create a webservice user and what capabilities are to be assigned to him. I've read the  Home - Site administration - Plugins - Web services - overview, but im not able to created a webservice user.

Kindly let me know what to enter in the fields required for creating a webservice user and capabilities to be assigned to him.

Regards,

Amit

In reply to amit gupta

Re: Troubleshooting web services

by Nik Okuntseff -

A user account for your web service is a normal account in Moodle, so you create it in a similar way. Manually - in Home - Site administration - Users - Accounts - Browse list of users. Click the Add a new user link.

But the additional steps are to create a new system role, assign capabilities to the role, then assign user account to the tole. Take a look at the link in my previous post, it explains the procedure with screenshots.

Capabilities needed depend on the function your web service uses.

In reply to Nik Okuntseff

How to get the calendar or the assignments of an User

by Ivan Herrera -

We are trying to create a function to add to your example code (The one that worked perfectly getting the courses) to get all the events of an user using "core_calendar_get_calendar_events" or all the assignments of an user using "mod_assign_get_assignments".

The trouble is on creating the new function since the GetCourses function works fine.

We think is because of the parameters, but we don't know how to construct the function and we are about to launch the app.

Can you give us an example or a hint from where we can get the functions parameters or how to construct the function?

By the way, this was still very USEFUL

Regards

In reply to Ivan Herrera

Re: How to get the calendar or the assignments of an User

by Major Dads -

Hi Nik,

Do you have a function to get the course completion status of a user, ie the date the course was completed, the time and the status.  Thank you.

In reply to Nik Okuntseff

Forum and assignemtn for course gives accessexception error

by Mac Shah -

Hi, i follow as above all steps and its working for me ,

but while test with below webservice i got permission error


i have added function to all required capability but i am not able to do access below webservice

1)mod_forum_get_forums_by_courses

2)mod_assign_get_assignments


i had set all capability but its gives me error


{
    "exception": "webservice_access_exception",
    "errorcode": "accessexception",
    "message": "Access control exception"
}