Stream file from remote service to S3 bucket - java

I have a microservice which needs to take a remote file, and upload it to a S3 bucket. The remote file is presented as a download link, and requires basic authentication.
Using the latest AWS 2.0 SDK I'm trying to stream the file so it doesn't have to all download to the server first. The files can be anywhere from 90kb -> 100GB+.
Using Spring Boot 2.0, I can't find if there is support in the new WebClient to handle this, so I'm trying to hack something together such as:
public Mono<String> upload(#PathVariable String projectId, #RequestBody String downloadLink) {
try {
String authString = properties.getSilverstripe().getUsername() + ":" + properties.getSilverstripe().getToken();
// Download link needs to be cleaned a little;
URL url = new URL(downloadLink.replaceAll("\"", ""));
URLConnection urlConnection = url.openConnection();
// Add basic authentication to the stream
urlConnection.setRequestProperty("Authorization", "Basic " + new String(Base64.encodeBase64(authString.getBytes())));
InputStream inputStream = urlConnection.getInputStream();
// Attempt to create a input stream and upload to the bucket
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
Stream<String> stream = reader.lines();
{
stream.forEach(part -> {
S3AsyncClient client = S3AsyncClient.create();
client.putObject(
PutObjectRequest.builder()
.bucket(BUCKET)
.key(projectId + "/" + LocalDate.now() + ".sspak")
.build(),
AsyncRequestProvider.fromString(part)
);
});
}
return Mono.just(downloadLink);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
I was hoping there would be a pretty standard library/pattern for doing this but I can't find much online.
Any help at all is appreciated.

Related

Microsoft Graph: Requesting an Extension returns http 400 bad request

I added an open extension to an event in a calendar and am trying to read it back.
Here is the url:
https://graph.microsoft.com/v1.0/users/{userid}/calendars/{calendarId}=/events?$expand=Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event')
I cannot get this to work in a Java program. The following combinations do work:
It works my Java program if I remove the $expand... parameter. I can also ask for certain fields, that works too.
The request works in Postman (I just have to set the token)
The request works in Graph Explorer when I log in as the owner of the calendar
Here is the extension (inside one of the events) when I use Postman to read the event. It is the last item in the event:
"extensions#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('{userid}')/calendars('{calendarId}')/events('{eventId})/extensions",
"extensions": [
{
"#odata.type": "#microsoft.graph.openTypeExtension",
"id": "Microsoft.OutlookServices.OpenTypeExtension.c.i.m.p.server.entities.outlook.Event",
"extensionName": "c.i.m.p.server.entities.outlook.Event",
"adherentId": "12346",
"timeSlotID": "346463"
}
]
Here is the Java code (Java 8, using java.io and java.net libraries):
private static void doSomething(String _accessToken) throws IOException {
String urlString = "https://graph.microsoft.com/v1.0/users/{userId}/calendars/{calendarId}/events?$expand=Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event')";
URL url = new URL(urlString);
Proxy webProxy
= new Proxy(Proxy.Type.HTTP, new InetSocketAddress({proxy-address}, {port}));
HttpURLConnection connection = (HttpURLConnection) url.openConnection(webProxy);
// Set the appropriate header fields in the request header.
connection.setRequestProperty("Authorization", "Bearer " + _accessToken);
connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
connection.setReadTimeout(5000);
connection.setRequestMethod(HttpMethod.GET);
try {
connection.connect();
int responseCode = connection.getResponseCode();
System.out.println("execute(), response code = " + responseCode);
String responseMessage = connection.getResponseMessage();
System.out.println("execute(), response Message = " + responseMessage);
String responseString = null;
try {
InputStream ins = connection.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(ins));
StringBuffer sb=new StringBuffer();
String line;
while ((line=br.readLine()) != null) {
sb.append(line);
}
responseString = sb.toString();
} catch (Exception e) {
System.out.println("Could not get input stream from response, error is " + e.toString());
}
System.out.println("execute(), httpResult = " + responseString);
} catch (IOException e) {
System.out.println(".execute(), IOException : " + e.toString());
} finally {
connection.disconnect();
}
}
How do I fix this? Thanks!
400 means bad request. It could be because of url encoding. Url encode the query string.
Something like
String query = "Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event'";
String url = "https://graph.microsoft.com/v1.0/users/{userId}/calendars/{calendarId}/events?
$expand=" + URLEncoder.encode(query, StandardCharsets.UTF_8.name());
Alternatively you could use graph service java api based on your need which will help abstract all the interactions for you or you could use any of the rest clients available.
First of all, you should provide more info on the error - Stacktrace and error message. But 400 code indicates that was a user mistake, meaning that you are sending an invalid request. Since you say that postman request works then compare all the headers that are sent by postman and see if your code misses some hearer. As for the code, instead of coding your own Http client functionality I would suggest using 3d party Http client. Here are a few suggestions:
Apache Http client - very popular and well known 3d party Http Client
OK Http client - Open-source Http client. Here is tutorial
MgntUtils Http client - very simple 3d party HttpClient: Provided in MgntUtils Open source library (written by me). Very simple in use. Take a look at Javadoc. Library itself provided as Maven artifacts and on Git (including source code and Javadoc).

Download files into system in which we are executing an API

I have a requirement where I need to download files from third party APIs.
I have written an API in my application which calls third party APIs and downloads files. I am able to download the files and unzip it successfully. These files are getting downloaded into tomcat server where the code is deployed when i hit my API.
But I would like to get those files downloaded into the system in which i am executing my API. Suppose, if i deploy that code into test environment server and execute my API using curl command from my local system, then files should get downloaded into my local system. Is there anyway I can achieve this in Java?
public class SnapshotFilesServiceImplCopy {
public static final ILogger LOGGER = FWLogFactory.getLogger();
private RestTemplate mxRestTemplate = new RestTemplate();
public void listSnapShotFiles(String diId, String snapshotGuid) {
LOGGER.debug("Entry - SnapshotFilesServiceImpl: FI=" + diId + " snapshotGuid=" + snapshotGuid);
ResponseEntity responseEntity = null;
HttpEntity entity = new HttpEntity(CommonUtil.getReportingAPIHeaders());
String resourceURL = "files_url";
try {
responseEntity = mxRestTemplate.exchange(resourceURL, HttpMethod.GET, entity, String.class);
} catch (RestClientException re) {
if (re instanceof HttpStatusCodeException) {
//TO be handled
}
}
String data = (String) responseEntity.getBody();
try {
Object obj = new JSONParser().parse(data);
JSONObject jsonObject = (JSONObject) obj;
JSONArray jsonArray = (JSONArray) jsonObject.get("accounts");
for (int i = 0; i < jsonArray.size(); i++) {
String accountFileURL = (String) jsonArray.get(i);
downloadAccountsData(diId, accountFileURL);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
public void downloadAccountsData(String diId, String accountsURL) {
LOGGER.debug("Entry - SnapshotFilesServiceImpl: FI=" + diId + " snapshotGuid=" + accountsURL);
ResponseEntity<Resource> responseEntity = null;
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", "application/vnd.mx.logs.v1+json");
headers.set("API-KEY", "key");
HttpEntity entity = new HttpEntity(CommonUtil.getReportingAPIHeaders());
String resourceURL = accountsURL;
try {
responseEntity = mxRestTemplate.exchange(resourceURL, HttpMethod.GET, entity, Resource.class);
} catch (RestClientException re) {
if (re instanceof HttpStatusCodeException) {
//To be handled
}
}
Date date = new Date();
String fileName = RenumberingConstants.SNAPSHOT_FILE_ACCOUNTS + date.getTime();
try {
FileOutputStream fileOutputStream = new FileOutputStream(fileName + ".gz");
byte[] bytes = IOUtils.toByteArray(responseEntity.getBody().getInputStream());
fileOutputStream.write(bytes);
} catch (Exception e) {
e.printStackTrace();
}
try {
FileInputStream fis = new FileInputStream(fileName + ".gz");
GZIPInputStream gis = new GZIPInputStream(fis);
FileOutputStream fos = new FileOutputStream(fileName + ".avro");
byte[] buffer = new byte[1024];
int len;
while ((len = gis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
//close resources
fos.close();
gis.close();
} catch (IOException e) {
e.printStackTrace();
}
File file = new File(fileName + ".gz");
boolean isDeleted = file.delete();
if (isDeleted)
System.out.println("File has been deleted successfully.." + fileName + ".gz");
else
System.out.println("Could not delete the file.." + fileName + ".gz");
}
}
You can store the file received from third party API in one file (new File()) object, and then can save that file object at desired location. I need to see the code snippet which downloads the file from third party API to answer precisely.
What you are doing is saving the file in server (as Java program in tomcat cannot access client machine) and not returning it to the client which is calling your API. You need to open another output stream, and return the file data to client machine using that stream. You can refer this tutorial on how to download a file using streams.
Suppose, if i deploy that code into QA environment and execute my API using curl command from my local system, then files should get downloaded into my local system. Is there anyway I can achieve this in Java?
It is only achievable in Java (or any language) if the file or files are returned as the HTTP response to the request that you made using curl.
Or ... I suppose ... if you set up an HTTP server on your local system and the QA system "delivered" the files (in effect, a reverse upload!) by making an HTTP request (API call) the HTTP server.

HTTP URL connection response

I am trying to hit the URL and get the response from my Java code.
I am using URLConnection to get this response. And writing this response in html file.
When opening this html in browser after executing the java class, I am getting only google home page and not with the results.
Whats wrong with my code, my code here,
FileWriter fWriter = null;
BufferedWriter writer = null;
URL url = new URL("https://www.google.co.in/?gfe_rd=cr&ei=aS-BVpPGDOiK8Qea4aKIAw&gws_rd=ssl#q=google+post+request+from+java");
byte[] encodedBytes = Base64.encodeBase64("root:pass".getBytes());
String encoding = new String(encodedBytes);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
connection.setRequestProperty("Accept-Charset", "UTF-8");
connection.setDoInput(true);
connection.setRequestProperty("Authorization", "Basic " + encoding);
connection.connect();
InputStream content = (InputStream) connection.getInputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(content));
String line;
try {
fWriter = new FileWriter(new File("f:\\fileName.html"));
writer = new BufferedWriter(fWriter);
while ((line = in.readLine()) != null) {
String s = line.toString();
writer.write(s);
}
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Same code works couple of days back, but not now.
The reason is that this url does not return search results it self. You have to understand google's working process to understand it. Open this url in your browser and view its source. You will only see lots of javascript there.
Actually, in a short summary, google uses Ajax requests to process search queries.
To perform required task you either have to use a headless browser (the hard way) which can execute javascript/ajax OR better use google search api as directed by anand.
This method of searching is not advised is supposed to fail, you must use google search APIs for this kind of work.
Note: Google uses some redirection and uses token, so even if you will find a clever way to handle it, it is ought to fail in long run.
Edit:
This is a sample of how using Google search APIs you can get your work done in reliable way; please do refer to the source for more information.
public static void main(String[] args) throws Exception {
String google = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=";
String search = "stackoverflow";
String charset = "UTF-8";
URL url = new URL(google + URLEncoder.encode(search, charset));
Reader reader = new InputStreamReader(url.openStream(), charset);
GoogleResults results = new Gson().fromJson(reader, GoogleResults.class);
// Show title and URL of 1st result.
System.out.println(results.getResponseData().getResults().get(0).getTitle());
System.out.println(results.getResponseData().getResults().get(0).getUrl());
}

REQUEST DENIED with Google Places API KEY for Server Web used in an Android application

I'm trying to get info from the API of Google Places for an Android application. To do that, first I have enabled this API in my Google Account.
Second, I have created an API KEY for Browser. I already have an API KEY Server due to another API.
So, in my code I have been tested with these two Keys and with both I've got always the same result!!!
{
"error_message" : "This service requires an API key.",
"html_attributions" : [],
"results" : [],
"status" : "REQUEST_DENIED"
}
The code that I'm using to make the call are ...
#Override
protected String doInBackground(LocationService... ls) {
JSONObject result = new JSONObject();
URL url;
HttpsURLConnection urlConnection;
// Making HTTP request
try {
//Define connection
url = new URL("https://maps.googleapis.com/maps/api/place/nearbysearch/json");
urlConnection = (HttpsURLConnection)url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("charset", "utf-8");
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.setUseCaches(false);
//Send data
String parameters = "?location=" + String.valueOf(ls[0].getLocation().getLatitude()) + "," + String.valueOf(ls[0].getLocation().getLongitude());
parameters+="&radius=5000";
parameters+="&types=restaurant|health|city_hall|gas_station|shopping_mall|grocery_or_supermarket";
parameters+="&sensor=false";
parameters+="&key=" + Constants.API_KEY_BROWSER_APPLICATIONS;
byte[] postData = parameters.getBytes(Charset.forName("UTF-8"));
int postDataLength = postData.length;
urlConnection.setRequestProperty("Content-Length", Integer.toString(postDataLength));
DataOutputStream data = new DataOutputStream(urlConnection.getOutputStream());
data.write(postData);
data.flush();
data.close();
Log.d(TAG, "Datos enviados");
Log.d(TAG, "ResponseCode: " + String.valueOf(urlConnection.getResponseCode()));
//Display what returns POST request
StringBuilder sb = new StringBuilder();
int HttpResult = urlConnection.getResponseCode();
if(HttpResult == HttpURLConnection.HTTP_OK){
String json;
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(),"utf-8"));
String line;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
//System.out.println(""+sb.toString());
Log.d(TAG, "json: " + sb.toString());
FileService file = new FileService();
file.writeLog(POIActivity.TAG, getClass().getName(), POIActivity.urlConnection + parameters);
file.writeLog(POIActivity.TAG, "doInBackground", sb.toString());
// Parse the String to a JSON Object
result = new JSONObject(sb.toString());
}else{
//System.out.println(urlConnection.getResponseMessage());
Log.d(TAG, "urlConnection.getResponseMessage(): " + urlConnection.getResponseMessage());
result = null;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
Log.d(TAG, "UnsuppoertedEncodingException: " + e.toString());
} catch (JSONException e) {
e.printStackTrace();
Log.d(TAG, "Error JSONException: " + e.toString());
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "IOException: " + e.toString());
}
// Return JSON Object
return result.toString();
}
When I make the call to the API I've got like ResponseCode = 200 and the call that I build is finally like that ...
https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=38.26790166666667,-0.7052183333333333&radius=5000&types=restaurant|health|city_hall|gas_station|shopping_mall|grocery_or_supermarket&sensor=false&key=API_KEY
Remember, like API_KEY I have used both, Api Key for server applications and Api Key for browser applications and I've got the same result with both.
Sincerely, I'm desperate with this problem because I don't know what I am doing wrong!!!
The problem is that you are not using the Google Places API for Android, you are using the Google Places API Web Service.
Here is an example of using Google Places API for Android, and here is an example of using the Google Places API Web Service. You are definitely using the latter.
Enable the Google Places API Web Service and it will work:
If you go to this link while signed into your Google Cloud Console account:
https://console.cloud.google.com/apis/library?filter=category:maps
This is the API that should be enabled:
From the documentation:
Note: You need an Android API key, not a browser key. You can use the same API key for your Google Maps Android API v2 apps and your Google Places API for Android apps.
Check this for more help.
For an easier way try latest GCM configuration file implementation and easily create project using their developer interface.
Enable Google services for your app

S3 Java- Multipart upload using a presigned URL?

I have a separate services that is managing files and s3 authentication. It produces presigned URLs, which I am able to use in other services to upload (and download) files.
I would like to take advantage of the Multipart upload sdk- currently the 'uploadToUrl' method seems to spend most of its time on getResponseCode, so it's difficult to provide user feedback. Also, the multipart upload seems much faster in my testing.
Ideally, I'd like to be able to create some AWSCredentials using a presigned URL instead of a secret key / access key for temporary use. Is that just a pipe dream?
//s3 service
public URL getUrl(String bucketName, String objectKey, Date expiration, AmazonS3 s3Client, HttpMethod method, String contentType) {
GeneratePresignedUrlRequest generatePresignedUrlRequest;
generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, objectKey);
generatePresignedUrlRequest.setMethod(method);
generatePresignedUrlRequest.setExpiration(expiration);
generatePresignedUrlRequest.setContentType(contentType);
URL s = s3Client.generatePresignedUrl(generatePresignedUrlRequest);
System.out.println(String.format("Generated Presigned URL: %n %S", s.toString()));
return s;
}
//Upload service
Override
public void uploadToUrl(URL url, File file) {
HttpURLConnection connection;
try {
InputStream inputStream = new FileInputStream(file);
connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("PUT");
OutputStream out =
connection.getOutputStream();
byte[] buf = new byte[1024];
int count;
int total = 0;
long fileSize = file.length();
while ((count =inputStream.read(buf)) != -1)
{
if (Thread.interrupted())
{
throw new InterruptedException();
}
out.write(buf, 0, count);
total += count;
int pctComplete = new Double(new Double(total) / new Double(fileSize) * 100).intValue();
System.out.print("\r");
System.out.print(String.format("PCT Complete: %d", pctComplete));
}
System.out.println();
out.close();
inputStream.close();
System.out.println("Finishing...");
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
System.out.printf("Successfully uploaded.");
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
A few years later, but digging around in the AWS Java SDK reveals that adding the following to GeneratePresignedUrlRequest works pretty well:
AmazonS3Client amazonS3Client = /* ... */;
GeneratePresignedUrlRequest request = /* ... */;
// the following are required to trigger the multipart upload API
request.addRequestParameter("uploadId", uploadIdentifier);
request.addRequestParameter("partNumber", Integer.toString(partNumber));
// the following may be optional but are recommended to validate data integrity during upload
request.putCustomRequestHeader(Headers.CONTENT_MD5, md5Hash);
request.putCustomRequestHeader(Headers.CONTENT_LENGTH, Long.toString(contentLength));
URL presignedURL = amazonS3Client.generatePresignedUrl(request);
(I haven't dug deeply enough to determine whether CONTENT_MD5 or CONTENT_LENGTH are required.)
with PHP, you can
$command = $this->s3client->getCommand ('CreateMultipartUpload', array (
'Bucket' => $this->rootBucket,
'Key' => $objectName
));
$signedUrl = $command->createPresignedUrl ('+5 minutes');
But I found no way so far how to acheive this with Java.
For a single PUT (or GET) operation, one can use generatePresignedUrl, but I wouldn't know how to apply this to multipart upload like with the PHP getCommand ('CreateMultipartUpload'/'UploadPart'/'CompleteMultipartUpload') methods.
For now I am exploring returning temporary credentials returned by my trusted code instead of a signed url.
http://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingTempSessionTokenJava.html

Categories