Batch fetching messages performance - gmail-api

I need to get the last 100 messages in the INBOX (headers only). For that I'm currently using the IMAP extension to search and then fetch the messages. This is done with two requests (SEARCH and then UID FETCH).
What's the Gmail API equivalent to fetching multiple messages in one request?
All I could find is a batch API, which seems way more cumbersome (composing a long list of messages:get requests wrapped in plain HTTP code).

It's pretty much the same in the Gmail API as in IMAP. Two requests: first is messages.list to get the message ids. Then a (batched) message.get to retrieve the ones you want. Depending on what language you're using the client libraries may help with the batch request construction.
A batch request is a single standard HTTP request containing multiple Google Cloud Storage JSON API calls, using the multipart/mixed content type. Within that main HTTP request, each of the parts contains a nested HTTP request.
From: https://developers.google.com/storage/docs/json_api/v1/how-tos/batch
It's really not that hard, took me about an hour to figure it out in python even without the python client libraries (just using httplib and mimelib).
Here's a partial code snippet of doing it, again with direct python. Hopefully it makes it clear that's there's not too much involved:
msg_ids = [msg['id'] for msg in body['messages']]
headers['Content-Type'] = 'multipart/mixed; boundary=%s' % self.BOUNDARY
post_body = []
for msg_id in msg_ids:
post_body.append(
"--%s\n"
"Content-Type: application/http\n\n"
"GET /gmail/v1/users/me/messages/%s?format=raw\n"
% (self.BOUNDARY, msg_id))
post_body.append("--%s--\n" % self.BOUNDARY)
post = '\n'.join(post_body)
(headers, body) = _conn.request(
SERVER_URL + '/batch',
method='POST', body=post, headers=headers)

Great reply!
If somebody wants to use a raw function in php to make batch requests for fetching emails corresponding to message ids, please feel free to use mine.
function perform_batch_operation($auth_token, $gmail_api_key, $email_id, $message_ids, $BOUNDARY = "gmail_data_boundary"){
$post_body = "";
foreach ($message_ids as $message_id) {
$post_body .= "--$BOUNDARY\n";
$post_body .= "Content-Type: application/http\n\n";
$post_body .= 'GET https://www.googleapis.com/gmail/v1/users/'.$email_id.
'/messages/'.$message_id.'?metadataHeaders=From&metadataHeaders=Date&format=metadata&key='.urlencode($gmail_api_key)."\n\n";
}
$post_body .= "--$BOUNDARY--\n";
$headers = [ 'Content-type: multipart/mixed; boundary='.$BOUNDARY, 'Authorization: OAuth '.$auth_token ];
$curl = curl_init();
curl_setopt($curl,CURLOPT_URL, 'https://www.googleapis.com/batch' );
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl,CURLOPT_CONNECTTIMEOUT , 60 ) ;
curl_setopt($curl, CURLOPT_TIMEOUT, 60 ) ;
curl_setopt($curl,CURLOPT_POSTFIELDS , $post_body);
curl_setopt($curl, CURLOPT_RETURNTRANSFER,TRUE);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER,0);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
$tmp_response = curl_exec($curl);
curl_close($curl);
return $tmp_response;
}
FYI the above function gets just the headers for the emails, in particular the From and Date fields, please adjust according to the api documentation https://developers.google.com/gmail/api/v1/reference/users/messages/get

In addition to MaK you can perform multiple batch requests using the google-api-php-client and Google_Http_Batch()
$optParams = [];
$optParams['maxResults'] = 5;
$optParams['labelIds'] = 'INBOX'; // Only show messages in Inbox
$optParams['q'] = 'subject:hello'; // search for hello in subject
$messages = $service->users_messages->listUsersMessages($email_id,$optParams);
$list = $messages->getMessages();
$client->setUseBatch(true);
$batch = new Google_Http_Batch($client);
foreach($list as $message_data){
$message_id = $message_data->getId();
$optParams = array('format' => 'full');
$request = $service->users_messages->get($email_id,$message_id,$optParams);
$batch->add($request, $message_id);
}
$results = $batch->execute();

here is the python version, using the official google api client. Note that I did not use the callback here, because I need to handle the responses in a synchronous way.
from apiclient.http import BatchHttpRequest
import json
batch = BatchHttpRequest()
#assume we got messages from Gmail query API
for message in messages:
batch.add(service.users().messages().get(userId='me', id=message['id'],
format='raw'))
batch.execute()
for request_id in batch._order:
resp, content = batch._responses[request_id]
message = json.loads(content)
#handle your message here, like a regular email object

solution from Walty Yeung is worked partially for my use case.
if you guys tried the code and nothing happens use this batch
batch = service.new_batch_http_request()

Related

Can I do transfer between portfolios with withdraw and deposit APIs? [coinbase-api]

We cannot use the following APIs to do transfer between portfolios.
“POST /withdrawals/coinbase-account" (url:https://api-public.sandbox.pro.coinbase.com/withdrawals/coinbase-account)
“POST /deposits/coinbase-account” (url:https://api-public.sandbox.pro.coinbase.com/deposit/coinbase-account)
The error msg is as follows:
ApiError(status 403 code=): Invalid scope COINBASE args({'data': '{"amount": 0.1, "currency": "BTC", "coinbase_account_id": "4b08d5e5-fe77-4249-b017-301e8890652a"}', 'headers': {'Content-type': 'application/json', 'CB-ACCESS-KEY': 'XXXXXXXXXXXX', 'CB-ACCESS-SIGN': 'xxxxxxxxxxxxxxxxxxxxxxxxx, 'CB-ACCESS-TIMESTAMP': '1594280190.878908', 'CB-ACCESS-PASSPHRASE': 'xxxxxxxxxxx'}, 'timeout': 30.0})
Let us know if these 2 APIs are for transfer between portfolios or not. However, if the API is ok, could you please demonstrate on the url and request payload for the following 2 use cases:
If I want to do transfer from profile A to profile B, using A's API keys(with transfer access) and A.withdrawals(asset, amount, coinbase_id = B)
If I want to do transfer from profile B to profile A, using A's API keys(with transfer access) and A.deposits(asset, amount, coinbase_id = B)
Especially, Does the coinbase_id stands for profile(potfolio) id or account(asset) id? How to obtain this id by API?
It's already a long time thread, I imagine you already found your answer but if not and for those interested, you can use the /profiles/transfer route to transfer funds between your Coinbase Pro portfolios.
Just you must have an API key created from the "from" portfolio with the transfer permission.
Example:
POST /profiles/transfer
{
"from": "86602c68-306a-4500-ac73-4ce56a91d83c",
"to": "e87429d3-f0a7-4f28-8dff-8dd93d383de1",
"currency": "EUR",
"amount": "100.00"
}
You can obtain an account ID via obtaining an Oauth2 authorization first. Once that is obtained you can use a Coinbase API (NOT PRO) call to obtain an account ID. I will not cover the Oauth2 calls since they are decently documented. The API call using PHP CURL is as follows:
//////////////////////
// IMPORTANT, not covered how to obtain this
// Assuming a variable which contains the Oauth2 access code is passed in
// with a variable name called
// $oauth_provided_access_token
// IMPORTANT
//////////////////////
// Obtain Timestamp
$API_VERSION = '2021-02-26';
$timestamp = json_decode(file_get_contents("https://api.coinbase.com/v2/time"), true)["data"]["epoch"];
// Define request
$req = "/v2/user";
// Define full URL, why Coinbase cannot parse this and obtain the request is strange
$url = "https://api.coinbase.com" . $req;
// Obtain API key/Secret (Do not put this in your PHP code, obtain it via environment variables
// Note that the key and secret are not required in this instance
// but many Coinbase API calls will need them
//$key = $_SERVER["HTTP_MY_COINBASE_API_KEY"];
//$secret = $_SERVER["HTTP_MY_COINBASE_API_SECRET"];
// Set up CURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, false );
curl_setopt($ch, CURLOPT_USERAGENT,'CoinbaseAPI');
curl_setopt($ch, CURLOPT_HTTPHEADER
, array ( "Authorization: Bearer " . $oauth_provided_access_token
, "CB-VERSION:" . $API_VERSION
, "CB-ACCESS-TIMESTAMP:" . $timestamp
)
);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true );
$response = curl_exec($ch); // Actual account info
$info = curl_getinfo($ch); // Get more debugging info
curl_close($ch);
$response_object=json_decode($response);
echo $response_object->{"data")->{"id"}; // User Account ID

How to send Twilio SMS via Web Service in Salesforce Apex

I want to send SMS from Twilio noticed they have libraries built for Java, .Net, Node, etc. so that we can use them if we are upto those technologies.
But I want to do the same from Salesforce Apex and trying to figure out how to build the Http parameters to make the authorization.
I tried to map with cURL example given in Twilio documentation and can't find header keys for Auth token.
Below is my current code and looking for how to set the authentication params.
req.setEndpoint(baseURL + '/2010-04-01/Accounts/account_sid/Messages.json');
req.setMethod('POST');
req.setHeader('to', EncodingUtil.urlEncode('+to_number', 'UTF-8'));
req.setHeader('from', EncodingUtil.urlEncode('+from_number', 'UTF-8'));
Http ht = new Http();
HttpResponse res = ht.send(req);
Updated request :
Blob headerValue = Blob.valueOf('my-twilio-account-sid:my-twilio-auth-token');
String authorizationHeader = 'BASIC ' + EncodingUtil.base64Encode(headerValue);
req.setHeader('Authorization', authorizationHeader);
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
String body = EncodingUtil.urlEncode('From=+from_number&To=+to_number&Body=Sample text from twilio', 'UTF-8');
req.setBody(body);
Http ht = new Http();
HttpResponse res = ht.send(req);
Response saying
Bad Request : A 'From' phone number is required.
The phone numbers don't go in the headers.
For the headers you will need
Content-Type: "application/x-www-form-urlencoded"
then you will need another header for authorization
Authorization: auth-string
where auth-string is a combination of the string Basic followed by a space followed by a base64 encoding of twilio-account-sid:twilio-auth-token (replace with your Twilio credentials joined by the colon) so the header will look something like
Authorization: "Basic ABCiDEdmFGHmIJKjLMN2OPQwR2S3TUVzABliCDE3FGc1HIo2JKL2MjNwOPcxQRSwTUc1Vzc0XmYhZAB3CDElFGH1Jw=="
The body of the POST request should contain key, value pairs of To, From and Body, something like
"From=" + twilio-phone-number + "&To=" + to-number + "&Body=" + message-body (replace with string values for phone numbers and message).
I hope this helps.

Perform HTTP Get with curl inside a page

I have this download link
It downloads a zip file when link is clicked oR pasted in browser address bar. If pasted in the browser, the browser performs an http GET and the response forces the browser to download the file.
I want to perform http get from within a php script and force the browser to download the file. How can i achieve this with cURL in php.
I have tried below code but it prints binary response on the browser.
$curl = curl_init();
// Set some options - we are passing in a useragent too here
curl_setopt_array($curl, array(
CURLOPT_URL => 'http://windows.php.net/downloads/pecl/releases/pthreads/3.1.0/php_pthreads-3.1.0-7.0-ts-vc14-x64.zip'
));
// Send the request & save response to $resp
$resp = curl_exec($curl);
// Close request to clear up some resources
curl_close($curl);
The one thing you can try is simply dump that content instead of capturing it with the right headers. This way your script/server serves as an intermediary instead of a user, as it is right now.
Thus:
$curl = curl_init();
// Set some options - we are passing in a useragent too here
curl_setopt_array($curl, array(
CURLOPT_URL => 'http://windows.php.net/downloads/pecl/releases/pthreads/3.1.0/php_pthreads-3.1.0-7.0-ts-vc14-x64.zip'
));
// Send the request & save response to $resp
$resp = curl_exec($curl);
// Close request to clear up some resources
curl_close($curl);
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="php_pthreads-3.1.0-7.0-ts-vc14-x64.zip"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . strlen($resp));
exit($resp);

Unable to download a document from google cloud storage

I am able to upload a document and download the document from google cloud storage for signed url using httpclient in java.But,when i put the same signed url in browser i am unable to download document for the link.I am getting following error
The request signature we calculated does not match the signature you
provided. Check your Google secret key and signing method.`
But when i mark check shared publicly check box in storage browser i am able to download from the generated signed url.But i want to allow a user to download a document from the browser without marking it as shared publicly.
.
I want to get confirm on some confusing part like
For document to get accessible by user who does not have google account after creating a signed url also i have to check shared publicly check box in storage browser?
But i think if the url is signed then it should not be check for shared publicly checkbox and user who does not have google account can access the document?But in my case it is not happening .According to link
https://developers.google.com/storage/docs/accesscontrol#About-CanonicalExtensionHeaders
it talks about Canonicalized_Extension_Headers .So i put in my request header
request.addHeader("x-goog-acl","public-read");
This is my code
// construct URL
String url = "https://storage.googleapis.com/" + bucket + filename +
"?GoogleAccessId=" + GOOGLE_ACCESS_ID +
"&Expires=" + expiration +
"&Signature=" + URLEncoder.encode(signature, "UTF-8");
System.out.println(url);
HttpClient client = new DefaultHttpClient();
HttpPut request = new HttpPut(url);
request.addHeader("Content-Type", contentType);
request.addHeader("x-goog-acl","public-read");// when i put this i get error
request.addHeader("Authorization","OAuth 1/zVNpoQNsOSxZKqOZgckhpQ");
request.setEntity(new ByteArrayEntity(data));
HttpResponse response = client.execute(request);
When i put request.addHeader("x-goog-acl","public-read");i get error
HTTP/1.1 403 Forbidden error .
.But when i remove this line it is uploaded successfully .It seems like i need to set
request.addHeader("x-goog-acl","public-read") to make it publicly accessible but on putting this on my code i am getting error.
.Any suggestion Please?
Finally Solved it.
To run singed url from browser you have to set HTTP header . In https://developers.google.com/storage/docs/accesscontrol#Construct-the-String
Content_Type Optional. If you provide this value the client (browser) must provide this HTTP header set to the same value.There is a word most.
So if you are providing Content_Type for sign string you must provide same Content_Type in browser http header.When i set Content_Type in browser header this error finally solved
this works for me:
set_include_path("../src/" . PATH_SEPARATOR . get_include_path());
require_once 'Google/Client.php';
function signed_storageURL($filename, $bucket, $p12_certificate_path, $access_id, $method = 'GET', $duration = 3600 )
{
$expires = time( ) + $duration*60;
$content_type = ($method == 'PUT') ? 'application/x-www-form-urlencoded' : '';
$to_sign = ($method."\n"."\n".$content_type."\n".$expires."\n".'/'.$bucket.'/'.$filename);
$signature = '';
$signer = new Google_Signer_P12(file_get_contents($p12_certificate_path), 'notasecret');
$signature = $signer->sign($to_sign);
$signature = urlencode( base64_encode( $signature ) );
return ('https://'.$bucket.'.commondatastorage.googleapis.com/'.$filename.'?GoogleAccessId='.$access_id.'&Expires='.$expires.'&Signature='.$signature);
}
$url = signed_storageURL(rawurlencode("áéíóú espaço & test - =.jpg"),'mybucket', 'mykey.p12','myaccount#developer.gserviceaccount.com');
echo ''.$url.'';

POST Request unable to receive the JSON respose in Salesforce

I am calling an API of FluidSurvey. when i make a POST request ... it post the request on the fluidSurvey but i didnt get the JSON response. rather it returns nothing. any suggestion??
my controller code
public class fluidSurvey{
public String tst{set;get;}
public String result{get;set;}
public PageReference chk() {
getData();
return null;
}
public void getData(){
String apiKey = 'xxxxxx';
String pwd = 'xxxxxx';
String u = 'https://app.fluidsurveys.com/api/v2/surveys/survey_id/responses/';
HttpRequest req = new HttpRequest();
Http http = new Http();
HTTPResponse res;
try{
req.setEndPoint(u);
req.setTimeout(2000);
req.setMethod('POST');
Blob headerValue = Blob.valueOf(apikey + ':' + pwd);
String authorizationHeader = 'Basic '+ EncodingUtil.base64Encode(headerValue);
req.setHeader('Authorization', authorizationHeader);
req.setHeader('Content-Type', 'application/json');
req.setHeader('Content-Length','31999');
res = http.send(req);
tst= res.toString();
catch(Exception e){
System.debug('Callout error: '+ e);
System.debug(tst+'--------'+res);
}
}
}
and the Apex page code is
<apex:page controller="fluidSurvey">
<apex:form >
<apex:pageBlock title="New Fluid Surveys API">
<apex:outputText value="{!tst}"></apex:outputText><br/>
<apex:pageBlockButtons location="bottom">
<apex:commandButton value="Submit" action="{!chk}"/>
</apex:pageBlockButtons>
</apex:pageBlock>
</apex:form>
and api documentation link is http://docs.fluidsurveys.com/api/surveys.html#getting-a-list-of-surveys..
FluidSurveys Dev here.
Looks like you're doing a POST request, which according to the docs is for creating a new response. But your function is named getData, so I'm assuming you want to get a list of responses?
Change the request type from GET to POST and it should start to work.
Also, the response type will be application/json, but you shouldn't be setting the request type to that encoding.
If I'm mistaken and you're looking to submit a new response, then this code wouldnt work because you're not actually passing any content.
As you can see by http://docs.fluidsurveys.com/api/surveys.html#submitting-a-new-response you need to actually pass a dictionary of question ids and answers. The best way to figure out what the ids are or what the format is, is to first look at the response returned from a GET request.
The problem with my code was that i setting a content-length header, but not setting any body,the server is diligently waiting for the 3199 byte body. so after using the setBody method my code properly returns a json response
I wanted to add to this answer in that I found that some messages from Apex being posted to external endpoints were getting dropped by firewalls on the other end due to intrusion detection rules.
Apparently, there are conditions where on the Apex end, outbound messages do not conform to certain construction rules that prevent man-in-the-middle attacks and some firewalls or IDS are blocking them. This will appear on the Apex side as a "Read Time Out".
The specific IDS rule is CVE-2009-3555 (http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-3555).
If you are experiencing read timeouts in Apex to external endpoints and can't isolate them to apex programming, you might do some logging on the destination firewall to see if this is the issue, and if it is, create an exception in that firewall for this type of case.

Resources