4

I'm trying to access the URL Shortener ( http://goo.gl/ ) via its API from within Delphi. However, the only result I get is: HTTP/1.0 400 Bad Request (reason: parseError)

Here is my code (on a form with a Button1, Memo1 and IdHTTP1 that has IdSSLIOHandlerSocketOpenSSL1 as its IOHandler. I got the necessary 32-bit OpenSSL DLLs from http://indy.fulgan.com/SSL/ and put them in the .exe's directory):

procedure TFrmMain.Button1Click(Sender: TObject);
    var html, actionurl: String;
    makeshort: TStringList;
begin
try
 makeshort := TStringList.Create;

 actionurl := 'https://www.googleapis.com/urlshortener/v1/url';
 makeshort.Add('{"longUrl": "http://slashdot.org/stories"}');

 IdHttp1.Request.ContentType := 'application/json';
 //IdHTTP1.Request.ContentEncoding := 'UTF-8'; //Using this gives error 415

 html := IdHTTP1.Post(actionurl, makeshort);
 memo1.lines.add(idHTTP1.response.ResponseText);

     except on e: EIdHTTPProtocolException do
        begin
            memo1.lines.add(idHTTP1.response.ResponseText);
            memo1.lines.add(e.ErrorMessage);
        end;
    end;

 memo1.Lines.add(html);
 makeshort.Free;
end;

Update: I have left off my API key in this example (should usually work well without one for a few tries), but if you want to try it with your own, you can substitute the actionurl string with 'https://www.googleapis.com/urlshortener/v1/url?key=<yourapikey>';

The ParseError message leads me to believe that there might be something wrong with the encoding of the longurl when it gets posted but I wouldn't know what to change.

I've been fuzzing over this for quite a while now and I'm sure the mistake is right before my eyes - I'm just not seeing it right now. Any help is therefore greatly appreciated!

Thanks!

user1579166
  • 117
  • 1
  • 2
  • 10
  • I found the answer myself after all, it works great and it's got nothing to do with the api key after all. Unfortunately, for some unfathomable reason I'm prevented from posting my own answer with the new code on this site for another 8 hours, because I'm a new user, and the result doesn't fit into the comment box. I don't know what that restriction is meant to accomplish. Well, too bad. – user1579166 Aug 06 '12 at 14:49

2 Answers2

4

As you discovered, the TStrings overloaded version of the TIdHTTP.Post() method is the wrong method to use. It sends an application/x-www-form-urlencoded formatted request, which is not appropriate for a JSON formatted request. You have to use the TStream overloaded version of the TIdHTTP.Post() method instead`, eg:

procedure TFrmMain.Button1Click(Sender: TObject); 
var
  html, actionurl: String; 
  makeshort: TMemoryStream; 
begin 
  try
    makeshort := TMemoryStream.Create; 
    try 
      actionurl := 'https://www.googleapis.com/urlshortener/v1/url'; 
      WriteStringToStream(makeshort, '{"longUrl": "http://slashdot.org/stories"}', IndyUTF8Encoding); 
      makeshort.Position := 0;

      IdHTTP1.Request.ContentType := 'application/json'; 
      IdHTTP1.Request.Charset := 'utf-8';

      html := IdHTTP1.Post(actionurl, makeshort); 
    finally
      makeshort.Free; 
    end;

    Memo1.Lines.Add(IdHTTP1.Response.ResponseText); 
    Memo1.Lines.Add(html); 
  except
    on e: Exception do 
    begin 
      Memo1.Lines.Add(e.Message); 
      if e is EIdHTTPProtocolException then
        Memo1.lines.Add(EIdHTTPProtocolException(e).ErrorMessage); 
    end; 
  end; 
end; 
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
2

From the URL shortener API docs:

Every request your application sends to the Google URL Shortener API needs to identify your application to Google. There are two ways to identify your application: using an OAuth 2.0 token (which also authorizes the request) and/or using the application's API key.

Your example does not contain code for OAuth or API key authentication.

To authenticate with an API key, the docs are clear:

After you have an API key, your application can append the query parameter key=yourAPIKey to all request URLs.

mjn
  • 36,362
  • 28
  • 176
  • 378
  • Thanks for your quick reply. An API key is not mandatory here (although, as they say, "highly recommended"). For a handful of attempts it should usually go through without one. Also, the error ("parseError") I have mentioned above does not point to an apikey problem, I think. Anyway, I have just tried it with my key by modifying the string "actionurl" thuswise: https://www.googleapis.com/urlshortener/v1/url?key= Changes nothing, error stays exactly the same. Any other ideas? – user1579166 Aug 06 '12 at 13:11
  • You can try it with curl and capture the request using Fiddler or WireShark, then compare them. Maybe the Post function with a TStringList parameter is not doing it right (the stream-based post could work better in current Indy versions) – mjn Aug 06 '12 at 13:17
  • Seems like a bit of an overkill at this stage, no? I'm also not sure that this is would actually be conducive, as the syntax and the approach is obviously quite different from Delphi. So what if it works with curl, that still means nothing for my Pascal code... You see, I think there was/is just an obvious typo or omission in my code that would catch someone's eye right away. I mean, just knowing whether any of you can actually reproduce the parse error with the same code would probably be helpful. – user1579166 Aug 06 '12 at 13:23
  • 1
    >>Post function with a TStringList parameter is not doing it right (the stream-based post could work better<< Could you give me an example of how this would work? I'm not exactly sure what you mean by that. Thanks! – user1579166 Aug 06 '12 at 13:31
  • I found the answer myself after all, it works great and it's got nothing to do with the api key after all. Unfortunately, for some unfathomable reason I'm prevented from posting my own answer with the new code on this site for another 8 hours, because I'm a new user. I don't know what that restriction is meant to accomplish. Well, too bad. – user1579166 Aug 06 '12 at 14:45