I wrote a small PERL script to fetch some data from an URL using PERL. Not being an experienced programmer, I used the examples I found here in Stackoverflow. However, I always get the response
{"error":{"code":2,"message":"post parameter request missing"}}
The script looks like this
my $uri = 'URL';
my $json = '{"sourceCountry":"DE","sourceStore":476,"targetCountry":"DE","targetStore":[869],"article":[110101]}';
my $req = HTTP::Request->new( 'POST', $uri );
$req->header( 'Content-Type' => 'application/json' );
$req->content( $json );
my $lwp = LWP::UserAgent->new;
$response = $lwp->request($req);
The complete response is this:
HTTP/1.1 200 OK
Connection: close
Date: Wed, 15 Jan 2014 14:29:06 GMT
Server: Apache
Content-Length: 63
Content-Type: application/json; charset=utf-8
Client-Date: Wed, 15 Jan 2014 14:29:06 GMT
Client-Peer: 10.200.10.74:80
Client-Response-Num: 1
X-Powered-By: PHP/5.3.3
{"error":{"code":2,"message":"post parameter request missing"}}
What did I wrong?
Try:
use HTTP::Request::Common 'POST';
my $lwp = LWP::UserAgent->new;
$lwp->request( POST $uri, 'Content-Type' => 'application/json', 'Content' => $json );
though that should do basically the same thing except also setting Content-Length.
Alternatively, if the error you mention is literally indicating there is supposed to be a POST parameter (aka form data parameter) named request, try:
use HTTP::Request::Common 'POST';
my $lwp = LWP::UserAgent->new;
$lwp->request( POST $uri, [ 'request' => $json ] );
Assuming that it is a JSON RPC 2 service that you are trying to contact, you are missing part of the structure.
my $json = '{
"jsonrpc":"2.0",
"id":1,
"method":"some-method",
"params":{"sourceCountry":"DE","sourceStore":476,"targetCountry":"DE","targetStore":[869],"article":[110101]}
}';
If it is a JSON RPC service you could use JSON::RPC::LWP; which combines LWP::UserAgent and JSON::RPC::Common. It uses Moose so there will be quite a few dependencies to install. ( It uses Moose because JSON::RPC::Common uses Moose. )
use JSON::RPC::LWP;
my $url = ...;
my $rpc = JSON::RPC::LWP->new(
agent => 'Example ',
);
my $response = $rpc->call(
$url, # uri
'some-method', # service
{
sourceCountry => "DE",
sourceStore => 476,
targetCountry => "DE",
targetStore => [869],
article => [110101],
} # JSON container
);
if( my $error = $response->error ){
print 'error: #', $error->code, ' "', $error->message, "\"\n";
}else{
print "success!\n";
use Data::Dumper;
print Dumper( $response->result );
}
Note that you don't give JSON::RPC::LWP a JSON string, you give it a data structure.
Related
My Perl script sends push notifications to an Apple APNS server. It works except when I try to send emojis (special characters).
My code
use DBI;
use JSON;
use Net::APNS::Persistent;
use Data::Dumper;
use Encode;
my $cfg;
my $apns;
...;
sub connect {
my ($sandbox, $cert, $key, $pass) = $cfg->getAPNSServer();
$apns = Net::APNS::Persistent->new({
sandbox => $sandbox,
cert => $cert,
key => $key,
}) or die("[-] Unable to connect to APNS server");
}
sub push {
my $msg = $_[1];
Logger::log(5, "[APNS Client] Got message ".Dumper($msg));
#Encode::_utf8_off($msg);
utf8::encode($msg);
my $pack = decode_json($msg);
my ($token, $payload) = #{$pack};
Logger::log(5, "Sending push with token: $token and Data: \n".Dumper($payload));
$apns->queue_notification(
$token,
$payload
);
$apns->send_queue;
}
So in the push subroutine I pass JSON data with the format given below. My problem is with the emoji character \x{2460}. You can see I added this line
utf8::encode($msg);
Before decoding the data. If I remove this line I get an error while decoding the JSON data
Wide character in subroutine entry at .....
With the above line added I can decode my JSON data. However when I try to write to the socket in the next line ($apns->send_queue) gives
Cannot decode string with wide characters at /usr/lib/perl/5.10/Encode.pm line 176
How do I solve this?
Message format (JSON)
["token",
{
"aps":{
"alert":"Alert: \x{2460}",
"content-available":1,
"badge":2,
"sound":"default.aiff"
},
"d":"Meta"
}
]
Dumper Output
[-] [ 2015-08-25T20:03:15 ] [APNS Client] Got message $VAR1 = "[\"19c360f37681035730a26cckjgkjgkj58b2d20326986f4265ee802c103f51\",{\"aps\":{\"alert\":\"Alert: \x{24bc}\",\"content-available\":1,\"badge\":2,\"sound\":\"default.aiff\"},\"d\":\"Meta\"}]";
[-] [ 2015-08-25T20:03:15 ] Sending push with token: 119c360f37681035730a26cckjgkjgkj58b2d20326986f4265ee802c103f51 and Data:
$VAR1 = {
'aps' => {
'alert' => "Alert: \x{24bc}",
'content-available' => 1,
'badge' => 2,
'sound' => 'default.aiff'
},
'd' => 'Meta'
};
[x] [ 2015-08-25T20:03:15 ] [APNS Client] Error writing to socket. Reconnecting : Cannot decode string with wide characters at /usr/lib/perl/5.10/Encode.pm line 176.
First of all, decode_json expects JSON encoded using UTF-8, so if you're starting with "decoded" JSON, it is proper to encode it as you did.
utf8::encode( my $json_utf8 = $json_uni );
my $data = decode_json($json_utf8);
However, it would have been simpler to use from_json.
my $data = from_json($json_uni);
Now on to your question. Whoever wrote Net::APNS::Persistent messed up big time. I looked at the source code, and they expect the alert message to be encoded using UTF-8. Adding the following will make your structure conform with the module's wonky expectation:
utf8::encode(
ref($payload->{aps}{alert}) eq 'HASH'
? $payload->{aps}{alert}{body}
: $payload->{aps}{alert}
);
It wouldn't surprise me if you ran into other issues. Notably, the modules uses the bytes module, a sure sign that something is being done incorrectly.
You probably have to UTF-8 encode the alert in $payload before sending it. You can also use from_json instead of decode_json to avoid the first encoding step:
sub push {
my $msg = $_[1];
Logger::log(5, "[APNS Client] Got message ".Dumper($msg));
my $pack = from_json($msg);
my ($token, $payload) = #{$pack};
Logger::log(5, "Sending push with token: $token and Data: \n".Dumper($payload));
# UTF-8 encode before sending.
utf8::encode($payload->{aps}{alert});
$apns->queue_notification(
$token,
$payload
);
$apns->send_queue;
}
I couldn't think of better keywords to Google this issue, so I apologize if this is a duplicate.
Here is my logout.pl script that basically erases cookie:
#!/usr/bin/perl -w
use strict;
use warnings;
use CGI;
my $q = new CGI;
print $q->header('text/html');
my $cookie = $q->cookie(
-name => 'CGISESSID',
-value => '',
-expires => '-1d'
);
print $q->header(-cookie=>$cookie);
print $q->redirect('welcome.pl');
exit;
When I run this script in a browser, it prints the following:
Set-Cookie: CGISESSID=; path=/; expires=Mon, 17-Feb-2014 09:05:42 GMT Date: Tue, 18 Feb 2014 09:05:42 GMT Content-Type: text/html; charset=ISO-8859-1 Status: 302 Found Location: welcome.pl
What I want, however, is for the browser to delete the cookie and redirect to welcome.pl.
When you print $q->header, that prints all the headers, including the blank line which signals the end of headers, making anything after it content. You need to only print $q->header once, no more.
There is actually one more problem you might not figure out on your own. The “clear” cookie you’re trying to send to expire the session must be sent with the redirect. The -w switch is not usually what you want, just the use warnings you have too. Also, redirect URLs RFC:MUST be absolute. "welcome.pl" will in most likelihood work but it’s not a good practice and I had relative URIs bite very badly in a modperl app once. So, amended–
#!/usr/bin/env perl
use strict;
use warnings;
use CGI;
use URI;
my $q = CGI->new;
my $cookie = $q->cookie(
-name => 'CGISESSID',
-value => '',
-expires => '-1d'
);
my $welcome = URI->new_abs("welcome.pl", $q->url);
print $q->redirect( -uri => $welcome,
-cookie => $cookie,
-status => 302 );
exit;
You should use $q->header only once in your script and that should be before using anything printable on page
However if I do return $response; all the data is perfectly displayed.
It seems like json_decode only works when I call the API via curl but that was when the API was in a separate app on another vhost. I'm re-integrating it into our main app but I'm unable to extract the data.
echo $response['error']
echo $response->error
return $response->error
none of the above works.
performing a foreach on $response, all I get is header info.
foreach ($response as $a)
{
echo $a;
}
returns
Cache-Control: no-cache Content-Type: application/json Date: Fri, 14 Feb 2014 09:24:02 GMT
my controller
$response = ApiHelper::getCcm($pid);
//convert raw json into array
$ja = json_decode($response,true);
var_dump($ja);
the apihelper
return Response::json(array(
'error' => false,
'data' => $data->toArray()
),200);
I want JSON response from cakePHP which i will render using backbone.js. But instead of JSON response i am getting default.ctp content also along with JSON response i dont't know why. Is there something which i can do not to include default.ctp content in JSON response?
here is my code to fetch JSON
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Cache-Control: no-store, no-cache, max-age=0, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
//header('Content-type: text/json');
header('Content-type: application/json');
header('Pragma: no-cache');
//header("X-JSON: ".$content_for_layout);
$response['status'] = $status;
//$response['data']['titleForLayout'] = $title_for_layout;
$response['data']['validationErrors'] = $this->validationErrors;
$response['data']['sessionFlash'] = $this->Session->read('Message.flash.message');
//$response['data']['data'] = $this->data;
$response['data'][$this->request->params['controller']]['output'] = isset($output)?$output:null;
$output = json_encode($response);
if (isset($this->params['url']['callback'])) {
echo $this->params['url']['callback'] . '(' . $output . ');';
} else {
echo $output;
}
?>
where data->output contains the rows fetched.
Please help me out.
I get JSON response but the problem is get default.ctp content surrounding the response which i don't want. is there a way to do it?
Looks like you want Request Handling
http://book.cakephp.org/2.0/en/core-libraries/components/request-handling.html
You need to set the layout to be ajax or an empty layout.
In the controller set this:
$this->layout = 'ajax';
Well, that would be a rather obscure topic but I'll give it a try, maybe someone will know the answer.
I am writing a little remote Node.js client for the Transmission BitTorrent Client. Communication is handled via RPC using JSON objects.
Here is the specification.
And my code (written in CoffeeScript, if that is a problem I can provide the equivalent JavaScript code also, just didn't want to make this question too long to read), the important part:
runRemoteCommand: (params) ->
# convert JSON object to string
params = JSON.stringify params #, null, " "
# set request options
options =
host: #config.host
port: #config.port
path: #config.rpcPath
auth: "#{#config.username}:#{#config.password}"
headers:
'Content-Type': 'application/json'
'Content-Length': params.length
method: 'GET'
# we don't know the session id yet
sessionId = false
# wrapped in a function so it could be run two times, not really a finished solution
run = () =>
# If the session id is provided, set the header, as in 2.3.1 of the specs
if sessionId
options.headers["X-Transmission-Session-Id"] = sessionId
# define the request object and a callback for the response
request = #http.get options, (response) =>
# log everything for debug purposes
console.log "STATUS: #{response.statusCode}"
console.log "HEADERS: #{JSON.stringify response.headers}"
response.setEncoding 'utf8'
response.on "data", (data) =>
console.log "BODY: #{data}"
# if status code is 409, use provided session id
if response.statusCode == 409
sessionId = response.headers["x-transmission-session-id"]
console.log "sessionId: #{sessionId}"
# running it immediately sometimes caused the remote server to provide a 501 error, so I gave it a timeout
setTimeout run, 5000
# no output here
request.on "error", (e) =>
console.log "ERROR: #{e}"
# actually send the request
request.write params
request.end()
# run our function
run()
The params variable is defined as:
params =
"arguments":
"filename": link
"method": "torrent-add"
"tag": 6667
Everything works fine until I set a valid session id. On the first time the run function is called, I get the following output (formatted it a little to be more eye-friendly):
STATUS: 409
HEADERS:
{
"server":"Transmission",
"x-transmission-session-id":"io4dOLm8Q33aSCEULW0iv74SeewJ3w1tP21L7qkdS4QktIkR",
"date":"Wed, 04 Apr 2012 08:37:37 GMT",
"content-length":"580",
"content-type":"text/html; charset=ISO-8859-1"
}
sessionId:
io4dOLm8Q33aSCEULW0iv74SeewJ3w1tP21L7qkdS4QktIkR
BODY: 409:
ConflictYour request had an invalid session-id
header.To fix this, follow these steps: When reading a
response, get its X-Transmission-Session-Id header and remember it
Add the updated header to your outgoing requests When you get this
409 error message, resend your request with the updated
headerThis requirement has been added to help prevent CSRF
attacks.X-Transmission-Session-Id:
io4dOLm8Q33aSCEULW0iv74SeewJ3w1tP21L7qkdS4QktIkR
Which is exactly what should be returned by the remote server when no session id is provided. However, after setting the session id in the header, the server doesn't respond. The second run call is fired and the request is sent (confirmed by placing some useful console.logs), but the response callback is never fired. I receive no response from the remote server and my application freezes waiting.
I'm pretty sure the error is on my side, not on the server's, because an out-of-the-box remote client for android works just fine when connecting to the same remote session.
Am I performing the request correctly? Especially the JSON part?
EDIT: A little test
I have written a little php script to test if the JSON-encoded request is ok and used it as a "fake" remote transmission. Here it is:
$headers = apache_request_headers();
// Simulate transmission's behavior
if (!isset($headers['X-Transmission-Session-Id'])) {
header("HTTP/1.0 409 Conflict");
header("X-Transmission-Session-Id: test");
}
print_r($headers);
// Is there a nicer way to get the raw request?
print_r(file_get_contents('php://input'));
And, personally, I don't see anything wrong in the data outputted by this test. After returning the 409 status code, the Node.js app properly assigns the session id for the request. The first print_r prints an array:
Array
(
[Content-type] => application/json
[Content-length] => 152
[X-Transmission-Session-Id] => test
[Host] => tp.localhost
[Connection] => keep-alive
)
The second one prints a string, which is a properly formatted JSON string (nothing more in it):
{
"arguments": {
"filename": "http://link-to-torrent"
},
"method": "torrent-add",
"tag": 6667
}
I really can't see what am I doing wrong. Some third-party clients which I tested with the same remote server work properly.
Havng the same issue i've done this class. I'm thinking better way do a getData, method. But it works.
http = require "http"
_ = require "underscore"
class Connect
constructor: (#login, #password, #host='127.0.0.1', #port=9091, #headers={}) ->
getData: (params)->
key = "x-transmission-session-id"
options = {
host: #host
port: #port
path: '/transmission/rpc',
method: 'POST',
headers: #headers || {},
auth: "#{ #login }:#{ #password }"
}
_.extend options, params || {}
req = http.request(options, (res)=>
if res.statusCode == 401
console.log "Auth errror"
else if res.statusCode == 409
auth_header={}
auth_header[key] = res.headers[key]
_.extend #headers, auth_header
#getData(params)
else if res.statusCode == 200
res.setEncoding 'utf8'
res.on('data', (chunk)->
#here should be an emmit of data
console.log chunk
)
else
console.log "Error #{ res.statusCode }"
)
req.write('data\n')
req.write('data\n')
req.end()
connector = new Connect "transmission", "password"
connector.getData()
Well, I was able to circumvent - but not solve - the problem, using mikeal's request, which also simplified my code. The most recent version looks like this:
runRemoteCommand: (params, callback = false) =>
options =
uri: #uri
method: "POST"
json: params
if #sessionId
options.headers =
"X-Transmission-Session-Id": #sessionId
request options, (error, response, body) =>
retVal =
success: false
end = true
if error
retVal.message = "An error occured: #{error}"
else
switch response.statusCode
when 409
if response.headers["x-transmission-session-id"]
#sessionId = response.headers["x-transmission-session-id"]
end = false
#.runRemoteCommand params, callback
else
retVal.message = "Session id not present"
when 200
retVal.success = true
retVal.response = body
else retVal.message = "Error, code: #{response.statusCode}"
callback retVal if end && callback
I'll leave this answer unaccepted for the time being because I still don't know what was wrong with the "raw" version.
#!/bin/bash
#-----------------------------------------------------------------------
#
DEBUG=0
HOST="192.168.1.65"
PORT="8181"
TRURL="http://$HOST:$PORT/transmission/rpc"
USER="admin"
PASSWORD="password1"
XTSID=""
#-------------------------------------
#
function getSID ()
{
local S="$1"
S=${S##*X-Transmission-Session-Id: }
S=${S%%</code>*}
echo $S
}
#-------------------------------------
function getData ()
{
local REQUEST="$1"
local RET=$(curl --silent -H "X-Transmission-Session-Id: $XTSID" \
-H "Content-type: application/json" \
-X POST \
-d "$REQUEST" \
--user $USER:$PASSWORD $TRURL)
((DEBUG)) && echo $XTSID
if [[ "$RET" =~ "409: Conflict" ]]
then
XTSID=$(getSID "$RET")
((DEBUG)) && echo "XTSID $XTSID"
RET=$(curl --silent -H "X-Transmission-Session-Id: $XTSID" \
-H "Content-type: application/json" \
-X POST \
-d "$REQUEST" \
--user $USER:$PASSWORD $TRURL)
fi
echo $RET
}
#-------------------------------------
R='{"method":"session-stats"}'
RET=$(getData "$R")
echo $RET