I've been working on a RESTful API, and having difficulty putting together a reference implementation that I can expand to the entire API I want.

Basing my work on the 'noteresource' module that Hugo has at https://github.com/hugowetterberg/services-3.x-sample, I made that into a proper module and found that the POST & PUT operations did not work. Thinking I may have screwed something up, I reduced the 'noteresource' module into a very bare framework, renaming it 'api_shell' and adding comments as a form of documentation as I figure things out. (I removed the database logic, and reverted to just 'simulating' such operations. All I really want is to see the API testing logic reach my services callbacks.)

Anyway, attached to this are zipped versions of both the api_shell and api_shell_test modules.
Instructions for how to setup this and run the tests are at the top of api_shell.module and api_shell_test.js. It is very similar to Hugo's original logic, but with a few changes such as the logic requires being launched from FireBug's javascript console like this:

gApiShell.note.test()

Additionally, there is a gApiShell.note.testRetrieve() for manually testing a Retrieve operation because in Hugo's original logic that was only called when a POST or PUT operation succeeds.

If anyone who has POST or PUT or Targeted Actions working in their code could take a glance, I would really appreciate it.

Once this is working, it will be a working REST api shell others can base their work upon. I intend to add these to the Services Handbook when they are working.

CommentFileSizeAuthor
api_shell_example.zip10.44 KBbsenftner

Comments

bsenftner’s picture

Version: 6.x-3.0-beta2 » 6.x-3.x-dev

I just tried moving to the nightly development snapshot from Jan-19th,2011.

I am seeing the same items working and the same items failing as outlined in the attached modules. (POST, PUT, and Targeted Actions don't work.) (Come to think of it, I don't have any Relationship example implemented yet...)

kylebrowning’s picture

Hrmm, POST and PUT both work for me, IM able to POST a new node, and PUT the same node with updated fields. The Comments resource has perfect examples of both PUT (update) and POST(new and actions). THe URL im using to access a targeted action would be, comment/loadNodeComments and passing an NID, the only non optional parameter. Can you explain whats not working for you and why this core resource isnt working with POST and PUT? It should be a perfect example.

bsenftner’s picture

Thanks for your comment, Kyle.

I'm seeing the Services Endpoint being hit and recognized as a Services Endpoint when I check 'recent hits' at admin/reports/hits. This is while trying POSTs or PUTs, but my Services callback is not being called.

Focusing only on POST for the moment, I was thinking the problem may be access permissions. (Even though I'm the super-user.) So I changed my Create operation's definition to call an access callback of my own:

   // snipped from the larger definition of an entire resource, here's just the Create operation:
     'create' => array(    'help'                    => 'Creates a note',
						'callback'                => '_api_shell_note_create',
			//'access callback'         => 'user_access',
			//'access arguments'        => array('API shell create'),
						'access callback'         => '_api_shell_note_access',
						'access arguments'        => array('create'),
						'access arguments append' => FALSE,
						'args' => array( array(    'name'        => 'data',
											    'type'        => 'array',
											    'description' => 'A note definition',
											    'source'      => 'data',
												'optional'    => FALSE,
											  ),
									   ),
      ),

Here's the access callback, only slightly different from Hugo's original:
(both GET & DELETE operations show this access callback being called, but neither PUT nor POST operations hit it)

function _api_shell_note_access($op, $args) {
  global $user;
  $access = FALSE;
  
  switch ($op) {
		case 'create':
          $access = user_access('API shell create');
		  _devReport('--- inital create access is "'.print_r($access,1).'"');
		  $access = $access || $user->uid == 1;
		  break;
		case 'view':
          $projNode = _api_shell_getnote($args[0]);
		  if ($projNode) {
			$access = user_access('API shell view any');
			$access = $access || $projNode->uid == $user->uid && user_access('API shell view own');
		  }
		  break;
        case 'update':
          $projNode = _api_shell_getnote($args[0]);
		  if ($projNode) {
			$access = user_access('API shell edit any');
            $access = $access || $projNode->uid == $user->uid && user_access('API shell edit own');
          }
		  break;
        case 'delete':
          $projNode = _api_shell_getnote($args[0]);
          if ($projNode) {
			$access = user_access('API shell delete any');
            $access = $access || $projNode->uid == $user->uid && user_access('API shell delete own');
          }
		  break;
  }

  
  if (!$access) $accessStr='false';
  else $accessStr='true';
  _devReport('_api_shell_note_access: op is "'.print_r($op,1).'" and access is "'.$accessStr.'", but returning true anyway');
  
  $access = true;
  return $access;
}

And finally, this is how I am doing a POST in javascript:
Other than slight changes, this is the same logic as Hugo's example in noteresource.js.

  // create a global object that will hold our api hooks,
  // global so I can examine in Firebug while developing.
  gApiShell = {};
  
  // under 'note' will go all our note resource logic:
  gApiShell.note = {
      apiPath: 'api_shell/dev/note'
  };

  // fails! (as in server side ack never occurs, & this callback here is never called)
  gApiShell.note.create = function (data, callback) {
    console.log('inside note.create, about to POST to ' + this.apiPath);
    $.ajax({ type:        "POST",
             url:         this.apiPath,
             data:        JSON.stringify(data),
             dataType:    'json',
             contentType: 'application/json',
             success:     callback
          });
  };

  // below is snipped from the 'Save' button's click handler:
  var data;
  data = { // populate the data object with information
        subject: $(subject).val(),
        note: $(text).val()
      };
  gApiShell.note.create(data, note_saved);

When executing a POST, I'm seeing the FireBug js console output "'inside note.create, about to POST to api_shell/dev/note", so that much looks correct. Checking admin/reports/hits, I see "api_shell/dev/note" as my site's last hit and it is labeled as a Services endpoint. But from there, my access callback is not being called, and I'm not sure where to look next...

Where would I find the 'entry point' where Services receives a raw POST or PUT? I'll trace it from there. My PUTs and POSTs are being seen as Service Endpoint hits, so I should be able to trace through the completely unfamiliar to me source and figure out where I'm being discarded...

bsenftner’s picture

Taking a look at the Services source for the first time, I realized that watchdog entries were being logged. Taking a look at the watchdog entry for my last POST attempt, I see:

Warning: Missing argument 4 for ServicesArgumentException::__construct(), called in C:\wamp\www\sites\all\modules\services\servers\rest_server\includes\RESTServer.inc on line 236 and defined in ServicesArgumentException->__construct() (line 66 of C:\wamp\www\sites\all\modules\services\services.runtime.inc).

So, it looks like getControllerArguments($controller, $path, $method, &$sources=array()) is barfing on my POST, looking for a missing 4th parameter

So, adding some print statements to the start of getControllerArguments() to simply show the input parameters, I'm seeing:

  • my Create controller is being passed in (yea!)
  • the path array is empty
  • the method string is "POST"
  • but the sources array is empty. That is probably the source of my problem.

However, looking over what I'm doing in my javascript, I'm not seeing where my bug is... as you can see in the last code block of my #3 post, how I do my POST is exactly from Hugo's example...

bsenftner’s picture

Well, I got POST working!

Very surprising, the bug was that I'd changed the Create operation's argument type to 'array' from 'struct' (probably a typo) and after changing it back to 'struct' my POST operation worked!

Should that be a cause of failure?

Tracking this same possible solution for my PUT operations, I also had the same typo that changed my PUT's argument type from 'struct' to 'array', which I've switched back to 'struct' but am still not getting my Update operation's callback being hit. But it looks like I have good momentum here... The same watchdog error is being triggered, so I can follow that. (it's late here. tomorrow...)

kylebrowning’s picture

Status: Active » Closed (works as designed)