Uploading a file via Post Request - java

I am attempting to upload images to SmugMug via HTTP Post as per their documentation. I have all the headers correct, but the part that is confusing me is setting the binary data in the body as stated:
This method requires a POST request with the binary data in the body
and all other metadata in the headers.
I have tried:
SMResponse response = builder.post(SMResponse.class, Files.readAllBytes(image.toPath()));
SMResponse response = builder.post(SMResponse.class, new String(Files.readAllBytes(image.toPath())));
SMResponse response = builder.post(SMResponse.class, new String(Base64.encode(Files.readAllBytes(image.toPath()))));
SMResponse response = builder.post(SMResponse.class, Base64.encode(Files.readAllBytes(image.toPath())));
My best guess was the first one would work, but all of these return:
{"stat":"fail","method":"smugmug.images.upload","code":5,"message":"system error"}
Here is the full method that does the uploading, in case I missed something:
public boolean upload(File image, int albumId, String caption, String keywords,
Boolean hidden, Integer imageId, Integer altitude, Float latitude,
Float longitude, boolean pretty) throws IOException, InvalidKeyException, NoSuchAlgorithmException, SmugMugException {
logger.debug("upload() called");
byte[] imageBytes = Files.readAllBytes(image.toPath());
WebResource resource = SmugMugAPI.CLIENT.resource("http://upload.smugmug.com/");
LoggingFilter logFilter = new LoggingFilter();
resource.addFilter(logFilter);
OAuthSecrets secrets = new OAuthSecrets().consumerSecret(smugmug.getConsumerSecret());
OAuthParameters oauthParams = new OAuthParameters().consumerKey(smugmug.getCosumerKey()).
signatureMethod("HMAC-SHA1").version("1.0");
// Create the OAuth client filter
OAuthClientFilter filter = new OAuthClientFilter(SmugMugAPI.CLIENT.getProviders(), oauthParams, secrets);
// Add the filter to the resource
if (smugmug.getToken() != null){
secrets.setTokenSecret(smugmug.getToken().getSecret());
oauthParams.token(smugmug.getToken().getId());
}
resource.addFilter(filter);
WebResource.Builder builder = resource.getRequestBuilder();
//User agent
builder = builder.header("User-Agent", smugmug.getAppName());
//API Version header
builder = builder.header("X-Smug-Version", "1.3.0");
//Response Type header
builder = builder.header("X-Smug-ResponseType", "JSON");
//Content-Length header
builder = builder.header("Content-Length", Long.toString(image.length()));
//Content-MD5 header
builder = builder.header("Content-MD5", DigestUtils.md5Hex(imageBytes));
//X-Smug-FileName header
builder = builder.header("X-Smug-FileName", image.getName());
//X-Smug-AlbumID header
builder = builder.header("X-Smug-AlbumID", Integer.toString(albumId));
//X-Smug-Caption header
if(caption != null){
builder = builder.header("X-Smug-Caption", caption);
}
//X-Smug-Caption header
if(keywords != null){
builder = builder.header("X-Smug-Keywords", keywords);
}
//X-Smug-Hidden header
if(hidden != null){
builder = builder.header("X-Smug-Hidden", hidden.toString());
}
//X-Smug-ImageID header
if(imageId != null){
builder = builder.header("X-Smug-ImageID", imageId.toString());
}
//X-Smug-Altitude header
if(altitude != null){
builder = builder.header("X-Smug-Altitude", altitude.toString());
}
//X-Smug-Latitude header
if(latitude != null){
builder = builder.header("X-Smug-Latitude", latitude.toString());
}
//X-Smug-Latitude header
if(longitude != null){
builder = builder.header("X-Smug-Longitude", longitude.toString());
}
//X-Smug-Pretty header
if(pretty){
builder = builder.header("X-Smug-Pretty", Boolean.toString(pretty));
}
SMResponse response = builder.post(SMResponse.class, new String(imageBytes));
if (!"ok".equals(response.getStat())) {
throw new SmugMugException(response);
}
return true;
}
Where have I gone wrong?
Tried just to see the response:
SMResponse response = builder.entity(image).post(SMResponse.class);
It actually sent back a blank response (no json) which is odd in itself, as I would have expected some message back. Here is the output:
Nov 21, 2012 11:55:48 PM com.sun.jersey.api.client.filter.LoggingFilter log
INFO: 1 * Client in-bound response
1 < 200
1 < Edge-Control: no-store
1 < X-SmugMug-Hiring: How to love what you do: http://www.smugmug.com/jobs/
1 < Date: Thu, 22 Nov 2012 05:55:48 GMT
1 < Content-Length: 0
1 < X-SmugMug-Values: 4/4 - It's the product, stupid
1 < Expires: Thu, 22 Nov 2012 05:55:49 GMT
1 < Connection: keep-alive
1 < Content-Type: application/json; charset=utf-8
1 < X-Powered-By: SmugMug/0.9
1 < Server: Apache
1 < Cache-Control: private, no-store, no-cache, max-age=1, must-revalidate
1 <

I am not exactly sure what happened, but I was able to get it working after finding the Upload Log in the SmugMug Account Settings (To get there go to Tools -> Account Settings -> Stats -> Uploads -> Details). Note that in the upload log there is a toggle to show only errors or all uploads.
Now on to the actual answer to how to set the actual "body" of the Post Request. The actual format should have been the first one I posted:
SMResponse response = builder.post(SMResponse.class, Files.readAllBytes(image.toPath()));
So either I messed up, and thought it was not working when it was, there was a problem on smugmug's end at the time, or there was something else in my code that was wrong, that got fixed in the process of me trying to fix this non-issue.

Related

Getting body bytes from HttpResponse in Netty client

I am trying to create HTTP client using netty and everything work, but i have hard time parsing the body. My pipeline looks like this:
pipeline.addLast(new HttpClientCodec())
pipeline.addLast(new HttpContentDecompressor())
pipeline.addLast(new HttpObjectAggregator(1024*10))
pipeline.addLast(new HttpClientHandler[A](key, metrics))
and client handler (written in scala)
class HttpClientHandler[A: BodyParser](key: AttributeKey[Callback[A]], metrics: Metrics)
extends SimpleChannelInboundHandler[FullHttpResponse]
with LazyLogging {
override def channelRead0(ctx: ChannelHandlerContext, msg: FullHttpResponse): Unit = {
val callback = ctx.channel().attr(key).get()
if (callback != null) {
val response = buildResponse(msg)
callback(response)
} else {
throw new Exception("Callback not present in channel context ... this is a bug")
}
}
private def buildResponse(msg: FullHttpResponse): Either[Throwable, Response[A]] = {
val result = {
try {
val parsedBody = BodyParser[A].parse(msg.content().asReadOnly())
if (msg.status() == HttpResponseStatus.OK) {
Right(Response.Ok(parsedBody))
} else {
Right(Response.Other(msg.status().code(), parsedBody))
}
} catch {
case e: Throwable =>
Left(e)
}
}
result.fold(metrics.bodyParseFailure, metrics.successfulResponse)
result
}
override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit = {
logger.warn(s"error observed for channel ${ctx.channel()}, closing", cause)
ctx.channel().attr(key).get().apply(Left(cause))
}
}
The main issue is that msg.content() also contains Http data (method, version, headers ...) but im only interested in body. What am i doing wrong? Thanks!
That's super strange... msg.content() is a ByteBuf which only should have the payload of the request / response included.
Turns out this was caused by testing this against echo server which repeats the whole http request back, as proven by:
$ curl -X POST localhost:3000/a/b/c -v
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 3000 (#0)
> POST /a/b/c HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sat, 19 Dec 2020 09:50:56 GMT
< X-Http-Echo-Server-Id: 9fcf5d57-2ac7-4a92-8aed-775879bf3ac2
< Content-Type: text/plain;charset=utf-8
< Transfer-Encoding: chunked
< Server: Jetty(9.4.24.v20191120)
<
POST /a/b/c HTTP/1.1
Accept: */*
User-Agent: curl/7.64.1
Host: localhost:3000
* Connection #0 to host localhost left intact
* Closing connection 0

How do I do a SIP telephone call

I want to constract a telephone-caller inside my java application. For this pupose I used a JAIN-SIP library. After the first INVITE the system needs Proxy-Authentication. The second invite is conscructed with the help of "AuthenticationHelperImpl.class":https://gitorious.org/0xdroid/external_nist-sip/source/1e0f37693341071f316852c8e05a08deef2b7fc4:java/gov/nist/javax/sip/clientauthutils/AuthenticationHelperImpl.java#L311, includes Proxy-Authentication header and lloks like:
INVITE sip:+11111111111#fpbx.de;maddr=fpbx.de SIP/2.0
Call-ID: 1c609509a43b721ab11c396c1e6ea9e7#192.168.17.107
CSeq: 2 INVITE
From: "77735hk6iu" <sip:77735hk6iu#fpbx.de>
To: "+111111111111111" <sip:+11111111111#fpbx.de>
Via: SIP/2.0/UDP 192.168.17.107:34567;rport;branch=z9hG4bK-383337-5bc4fd6b7a616843fce9eaa243bcb10e
Max-Forwards: 70
Contact: <sip:77735hk6iu#192.168.17.107:5060>
Content-Type: application/sdp
Proxy-Authorization: Digest username="77735hk6iu",realm="fpbx.de",nonce="VLaIxVS2h5muPS30F2zLdXHjup6ELyen",uri="sip:+111111111111#fpbx.de:5060;maddr=fpbx.de",response="47ea578c6b01c99fd3ed2b41c60983df"
Content-Length: 61
v=0
o=- 130565705777141827 1 IN IP4 192.168.17.107
s=call
After that I receive at the beginning code 100 message ("your call is very important for us") followed with 408 code message ("Request Timeout").
What I did to imporve the situation:
tried different phone number formats: 004930208488480,
04930208488480, 049, 0049, sdfhajfkhsk. For all these numbers I
become the same combination on messages.
tried to use port in request uri
tried to remove maddr from request uri.
tried to fullfill the message body with codek settings.
to set and remove rport from via header
If you now what I'm doing wrong, please, help me.
Thank you in advance.
I think, Maybe your Proxy-Authorization header is wrong. Maybe you is miscalculated. I wanted to share my resolve.
authUser is your phoneNumber. (for example: 77735hk6iu )
authPass is your user's password.
msg is your invite request.(Headers !)
AccountManagerImpl accountManagerImp = new AccountManagerImpl(authUser, AuthPass);
AuthenticationHelperImpl authenticationHelperImpl = new AuthenticationHelperImpl(accountManagerImp);
try {
this.authentication = authenticationHelperImpl.handleChallenge(msg, (SIPClientTransaction)trans);
AuthenticationHelperImple.java Class :
public AuthorizationHeader handleChallenge(Response challenge, ClientTransaction challengedTransaction) throws SipException {
SIPRequest challengedRequest = ((SIPRequest) challengedTransaction.getRequest());
ListIterator authHeaders = null;
if (challenge.getStatusCode() == Response.UNAUTHORIZED) {
authHeaders = challenge.getHeaders(WWWAuthenticateHeader.NAME);
}
else {
if (challenge.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED) {
authHeaders = challenge.getHeaders(ProxyAuthenticateHeader.NAME);
}
else {
throw new IllegalArgumentException("Unexpected status code ");
}
}
if (authHeaders == null) {
throw new IllegalArgumentException("Could not find WWWAuthenticate or ProxyAuthenticate headers");
}
WWWAuthenticateHeader authHeader = null;
while (authHeaders.hasNext()) {
authHeader = (WWWAuthenticateHeader) authHeaders.next();
String realm = authHeader.getRealm();
this.uri = challengedRequest.getRequestURI();
this.requestMethod = challengedRequest.getMethod();
this.requestBody = (challengedRequest.getContent() == null) ? "" : new String(challengedRequest.getRawContent());
if (this.accountManager instanceof SecureAccountManager) {
UserCredentialHash credHash = ((SecureAccountManager) this.accountManager).getCredentialHash(challengedTransaction,
realm);
if (credHash == null) {
logger.logDebug("Could not find creds");
throw new SipException("Cannot find user creds for the given user name and realm");
}
this.authorizationHeader = this.getAuthorization(requestMethod, uri.toString(), requestBody, authHeader, credHash);
}
else {
UserCredentials userCreds = ((AccountManager) this.accountManager).getCredentials(challengedTransaction, realm);
if (userCreds == null) {
throw new SipException("Cannot find user creds for the given user name and realm");
}
// sipDomain = userCreds.getSipDomain();
// we haven't yet authenticated this realm since we were
// started.
this.authorizationHeader = this.getAuthorization(requestMethod, uri.toString(), requestBody, authHeader, userCreds);
}
}
return this.authorizationHeader;
}
getAuthorization function :
public AuthorizationHeader getAuthorization(String method,
String uri,
String requestBody,
WWWAuthenticateHeader authHeader,
UserCredentials userCredentials) throws SecurityException {
String response = null;
String qopList = authHeader.getQop();
String qop = (qopList != null) ? "auth" : null;
String nc_value = "00000001";
String cnonce = "xyz";
try {
response = MessageDigestAlgorithm.calculateResponse(authHeader.getAlgorithm(),
userCredentials.getUserName(), authHeader.getRealm(),userCredentials.getPassword(), authHeader.getNonce(), nc_value, // JvB added
cnonce, // JvB added
method, uri, requestBody, qop,logger);
}
catch (NullPointerException exc) {
throw new SecurityException("The received authenticate header was malformatted: " + exc.getMessage());
}
AuthorizationHeader authorization = null;
try {
if (authHeader instanceof ProxyAuthenticateHeader) {
if (this.headerFactory != null) {
authorization = headerFactory.createProxyAuthorizationHeader(authHeader.getScheme());
}
else {
authorization = new ProxyAuthorization();
authorization.setScheme(authHeader.getScheme());
}
}
else {
if (this.headerFactory != null) {
authorization = headerFactory.createAuthorizationHeader(authHeader.getScheme());
}
else {
authorization = new Authorization();
authorization.setScheme(authHeader.getScheme());
}
}
authorization.setUsername(userCredentials.getUserName());
authorization.setRealm(authHeader.getRealm());
authorization.setNonce(authHeader.getNonce());
authorization.setParameter("uri", uri);
authorization.setResponse(response);
if (authHeader.getAlgorithm() != null) {
authorization.setAlgorithm(authHeader.getAlgorithm());
}
if (authHeader.getOpaque() != null) {
authorization.setOpaque(authHeader.getOpaque());
}
// jvb added
if (qop != null) {
authorization.setQop(qop);
authorization.setCNonce(cnonce);
authorization.setNonceCount(Integer.parseInt(nc_value));
}
authorization.setResponse(response);
} catch (ParseException ex) {
throw new RuntimeException("Failed to create an authorization header!");
}
return authorization;
}
Finally, your this.authentication variable is ProxyAuthorizationHeader. You must put this.authentication in your INVITE message. And than you will sent SipMessage from transaction or dialog to JAIN-SIP stack.
Good Luck !
The problem was partly solved when a removed "maddr=fpbx.de" from request URI and from proxy-auth. uri
fpr this a used handleCahllenge method with boolean arguments:
inviteTid = authenticationHelper.handleChallenge(response, tid, sipProvider, 15, **true**);
But I still don't know how I can a acchieve sponaneous telephone number.
The 100 message is hop-by-hop, that is to say it just means the next hop got your request. Other messages will typically be end-to-end (so, if you got a 180 Ringing, that typically means the endpoint being called sent the 180). A 408 typically shows up when one of the hops sent the INVITE but never got a response (and your SIP stack might be generating that internally when it doesn't get a provisional response in a reasonable timeframe -- usually about 32 seconds with the default SIP timers).
I don't know your network setup, but there are several private IPs in that message (of the 192.168.x.x variety). If I had to guess, your first hop is sending the 100 back to the IP/port it received it from, but the next response is following the Via headers (as it should), and the hop after you isn't respecting the rport parameter, so the response is getting lost. Alternately, your NAT is poorly configured and is closing the hole it created for the INVITE too quickly.
If you have a proxy on the edge of your network that this message is going out, it is either putting bad Via headers on the message (possibly with the internal IP instead of the external IP) or it is sending the INVITE to the wrong place (causing it to never get a response), and the 408 is coming from it.

Jersey JAXB POST adds a linebreak

I'm trying to post an entity to a webservice, here the code:
EmptyTagWrapper wrap = webResource.get(EmptyTagWrapper.class);
client = Client.create();
client.addFilter(new LoggingFilter(System.out));
webResource = client.resource(MyWebservice.CREATETAGS.getUrl());
ClientResponse clientResponse = webResource.post(ClientResponse.class, wrap);
System.out.println(clientResponse);
If I try to do this manually it works:
http://www.mysite.de/api/tags/?xml=<?xml version="1.0" encoding="UTF-8"?><prestashop xmlns:xlink="http://www.w3.org/1999/xlink"><tag><name>Testtag</name><id_lang>1</id_lang></tag></prestashop>
but if I run my program, I get this error:
<?xml version="1.0" encoding="UTF-8"?>
<prestashop xmlns:xlink="http://www.w3.org/1999/xlink">
<errors>
<error>
<code><![CDATA[127]]></code>
<message><![CDATA[XML error : String could not be parsed as XML
XML length : 0
Original XML : ]]></message>
</error>
</errors>
</prestashop>
I think the reason is the linebreak that is appendet between the URL and the marshaled entity, here the sysout of the post-request:
1 * Client out-bound request
1 > POST http://www.mysite.de/api/tags/?xml=
<?xml version="1.0" encoding="UTF-8"?><prestashop xmlns:xlink="http://www.w3.org/1999/xlink"><tag><name>Testtag</name><id_lang>1</id_lang></tag></prestashop>
1 * Client in-bound response
1 < 500
1 < Execution-Time: 0.006
1 < Server: Apache
1 < Access-Time: 1404134558
1 < Connection: close
1 < Vary: Host
1 < PSWS-Version: 1.5.4.1
1 < Content-Length: 282
1 < Date: Mon, 30 Jun 2014 13:22:38 GMT
1 < Content-Type: text/xml;charset=utf-8
1 < X-Powered-By: PrestaShop Webservice
1 <
Any Ideas how to solve this?
Background
I've got different services and fields which are constructed depending on the entity and the service:
public enum PrestaWebservice {
PRODUCTLINKS("Key1","products"),
FULLPRODUCTLIST("Key1","products?display=["+PrestaProduct.FIELDS+"]"),
CATEGORIESLIST("Key1","categories?display=["+PrestaCategory.FIELDS+"]"),
CUSTOMERSLIST("Key2","customers?display=["+PrestaCustomer.FIELDS+"]"),
TAGSLIST("Key2","tags?display=full"),
EMPTYTAG("Key2","tags?schema=synopsis"),
CREATETAGS("Key2","tags/?xml=");
private final String SHOPADDRESS="http://www.mySite.de/api";
private String key;
private String url;
private PrestaWebservice(String key, String url){
this.key = key;
this.url = url;
}
public String getKey(){
return key;
}
public String getUrl(){
return SHOPADDRESS+"/"+url;
}
}
In your question the URL is being constructed in your own code. You need to example in the following area why the carriage return is being added.
MyWebservice.CREATETAGS.getUrl()
Is there a reason you are send the XML as the value of a query parameter instead of in the body of the HTTP request?

Bounced mails get Status header

I am trying to read the Status header of a bounced email. This site explains better what I am trying...
The original email is composed by several MultiParts objects, so I am reading it in java code:
private void test(MimeMessage message) throws IOException, MessagingException {
if (message.getContent() != null && message.getContent() instanceof Multipart) {
Multipart content = (Multipart) message.getContent();
for (int i = 0; i < content.getCount(); i++) {
BodyPart bodyPart = content.getBodyPart(i);
Enumeration headers = bodyPart.getAllHeaders();
while(headers.hasMoreElements()){
Header header = (Header) headers.nextElement();
LOGGER.info("Header: " + header.getName() + " value: " + header.getValue());
}
}
}
}
The email part I am analyzing:
Content-Description: Delivery report Content-Type: text/plain;
charset=utf-8 Content-Transfer-Encoding: 7bit
Reporting-MTA: dns; someLink.com
X-Postfix-Queue-ID: EC862F00D0 X-Postfix-Sender: rfc822;
receiver#email.com Arrival-Date: Wed, 7 Aug 2013
13:52:43 +0200 (CEST)
Final-Recipient: rfc822; noexisting#email.com
Original-Recipient: rfc822;noexisting#email.com Action:
failed Status: 5.1.1 Remote-MTA: dns; [somelink.com
Diagnostic-Code: smtp; 550-5.1.1 The email account that you tried to
reach does
not exist. Please try 550-5.1.1 double-checking the recipient's email
address for typos or 550-5.1.1 unnecessary spaces.
In my log file I can see only the 3 first headers:
> Header: Content-Description value: Delivery report
> Header: Content-Type value: text/plain; charset=us-ascii INFO
> Header: Content-Transfer-Encoding value: 7bit
Does anyone know why? How could I get the status header? Thanks
I couldnĀ“t find the Status information in the header, and I will take it from the content. It is not an elegant solution, but at least it works.
If someone finds a better one, please let me know!
Java code:
StringWriter writer = new StringWriter();
IOUtils.copy(bodyPart.getInputStream(), writer);
LOGGER.info("Content inputstream: " + writer.toString());
Logs:
Content inputstream: Reporting-MTA: dns; srvvie-mx3.styria-multi-media.com
X-Postfix-Queue-ID: 2A1A8F00CF X-Postfix-Sender: rfc822;
Arrival-Date: Fri, 9 Aug 2013
11:14:02 +0200 (CEST)
Final-Recipient: rfc822; MAILER-DAEMON#domain.com
Original-Recipient: rfc822;MAILER-DAEMON#domain.com
Action: failed Status: 5.1.1 Remote-MTA: dns;
Diagnostic-Code: smtp; 550 5.1.1 Mailbox
does not exist

Upload a video file by chunks

Yes, it's a long question with a lot of detail... So, my question is: How can I stream an upload to Vimeo in segments?
For anyone wanting to copy and debug on their own machine: Here are the things you need:
My code here.
Include the Scribe library found here
Have a valid video file (mp4) which is at least greater than 10 MB and put it in the directory C:\test.mp4 or change that code to point wherever yours is.
That's it! Thanks for helping me out!
Big update: I've left a working API Key and Secret for Vimeo in the code here. So as long as you have a Vimeo account, all the code should work just fine for you once you've allowed the application and entered your token. Just copy the code from that link into a project on your favorite IDE and see if you can fix this with me. I'll give the bounty to whoever gives me the working code. Thanks! Oh, and don't expect to use this Key and Secret for long. Once this problem's resolved I'll delete it. :)
Overview of the problem: The problem is when I send the last chunk of bytes to Vimeo and then verify the upload, the response returns that the length of all the content is the length of only the last chunk, not all the chunks combined as it should be.
SSCCE Note: I have my entire SSCCE here. I put it somewhere else so it can be C ompilable. It is NOT very S hort (about 300 lines), but hopefully you find it to be S elf-contained, and it's certainly an E xample!). I am, however, posting the relevant portions of my code in this post.
This is how it works: When you upload a video to Vimeo via the streaming method (see Upload API documentation here for setup to get to this point), you have to give a few headers: endpoint, content-length, and content-type. The documentation says it ignores any other headers. You also give it a payload of the byte information for the file you're uploading. And then sign and send it (I have a method which will do this using scribe).
My problem: Everything works great when I just send the video in one request. My problem is in cases when I'm uploading several bigger files, the computer I'm using doesn't have enough memory to load all of that byte information and put it in the HTTP PUT request, so I have to split it up into 1 MB segments. This is where things get tricky. The documentation mentions that it's possible to "resume" uploads, so I'm trying to do that with my code, but it's not working quite right. Below, you'll see the code for sending the video. Remember my SSCCE is here.
Things I've tried: I'm thinking it has something to do with the Content-Range header... So here are the things I've tried in changing what the Content-Range header says...
Not adding content range header to the first chunk
Adding a prefix to the content range header (each with a combination of the previous header):
"bytes"
"bytes " (throws connection error, see the very bottom for the error) --> It appears in the documentation that this is what they're looking for, but I'm pretty sure there are typos in the documentation because they have the content-range header on their "resume" example as: 1001-339108/339108 when it should be 1001-339107/339108. So... Yeah...
"bytes%20"
"bytes:"
"bytes: "
"bytes="
"bytes= "
Not adding anything as a prefix to the content range header
Here's the code:
/**
* Send the video data
*
* #return whether the video successfully sent
*/
private static boolean sendVideo(String endpoint, File file) throws FileNotFoundException, IOException {
// Setup File
long contentLength = file.length();
String contentLengthString = Long.toString(contentLength);
FileInputStream is = new FileInputStream(file);
int bufferSize = 10485760; // 10 MB = 10485760 bytes
byte[] bytesPortion = new byte[bufferSize];
int byteNumber = 0;
int maxAttempts = 1;
while (is.read(bytesPortion, 0, bufferSize) != -1) {
String contentRange = Integer.toString(byteNumber);
long bytesLeft = contentLength - byteNumber;
System.out.println(newline + newline + "Bytes Left: " + bytesLeft);
if (bytesLeft < bufferSize) {
//copy the bytesPortion array into a smaller array containing only the remaining bytes
bytesPortion = Arrays.copyOf(bytesPortion, (int) bytesLeft);
//This just makes it so it doesn't throw an IndexOutOfBounds exception on the next while iteration. It shouldn't get past another iteration
bufferSize = (int) bytesLeft;
}
byteNumber += bytesPortion.length;
contentRange += "-" + (byteNumber - 1) + "/" + contentLengthString;
int attempts = 0;
boolean success = false;
while (attempts < maxAttempts && !success) {
int bytesOnServer = sendVideoBytes("Test video", endpoint, contentLengthString, "video/mp4", contentRange, bytesPortion, first);
if (bytesOnServer == byteNumber) {
success = true;
} else {
System.out.println(bytesOnServer + " != " + byteNumber);
System.out.println("Success is not true!");
}
attempts++;
}
first = true;
if (!success) {
return false;
}
}
return true;
}
/**
* Sends the given bytes to the given endpoint
*
* #return the last byte on the server (from verifyUpload(endpoint))
*/
private static int sendVideoBytes(String videoTitle, String endpoint, String contentLength, String fileType, String contentRange, byte[] fileBytes, boolean addContentRange) throws FileNotFoundException, IOException {
OAuthRequest request = new OAuthRequest(Verb.PUT, endpoint);
request.addHeader("Content-Length", contentLength);
request.addHeader("Content-Type", fileType);
if (addContentRange) {
request.addHeader("Content-Range", contentRangeHeaderPrefix + contentRange);
}
request.addPayload(fileBytes);
Response response = signAndSendToVimeo(request, "sendVideo on " + videoTitle, false);
if (response.getCode() != 200 && !response.isSuccessful()) {
return -1;
}
return verifyUpload(endpoint);
}
/**
* Verifies the upload and returns whether it's successful
*
* #param endpoint to verify upload to
* #return the last byte on the server
*/
public static int verifyUpload(String endpoint) {
// Verify the upload
OAuthRequest request = new OAuthRequest(Verb.PUT, endpoint);
request.addHeader("Content-Length", "0");
request.addHeader("Content-Range", "bytes */*");
Response response = signAndSendToVimeo(request, "verifyUpload to " + endpoint, true);
if (response.getCode() != 308 || !response.isSuccessful()) {
return -1;
}
String range = response.getHeader("Range");
//range = "bytes=0-10485759"
return Integer.parseInt(range.substring(range.lastIndexOf("-") + 1)) + 1;
//The + 1 at the end is because Vimeo gives you 0-whatever byte where 0 = the first byte
}
Here's the signAndSendToVimeo method:
/**
* Signs the request and sends it. Returns the response.
*
* #param service
* #param accessToken
* #param request
* #return response
*/
public static Response signAndSendToVimeo(OAuthRequest request, String description, boolean printBody) throws org.scribe.exceptions.OAuthException {
System.out.println(newline + newline
+ "Signing " + description + " request:"
+ ((printBody && !request.getBodyContents().isEmpty()) ? newline + "\tBody Contents:" + request.getBodyContents() : "")
+ ((!request.getHeaders().isEmpty()) ? newline + "\tHeaders: " + request.getHeaders() : ""));
service.signRequest(accessToken, request);
printRequest(request, description);
Response response = request.send();
printResponse(response, description, printBody);
return response;
}
And here's some (an example... All of the output can be found here) of the output from the printRequest and printResponse methods: NOTE This output changes depending on what the contentRangeHeaderPrefix is set to and the first boolean is set to (which specifies whether or not to include the Content-Range header on the first chunk).
We're sending the video for upload!
Bytes Left: 15125120
Signing sendVideo on Test video request:
Headers: {Content-Length=15125120, Content-Type=video/mp4, Content-Range=bytes%200-10485759/15125120}
sendVideo on Test video >>> Request
Headers: {Authorization=OAuth oauth_signature="zUdkaaoJyvz%2Bt6zoMvAFvX0DRkc%3D", oauth_version="1.0", oauth_nonce="340477132", oauth_signature_method="HMAC-SHA1", oauth_consumer_key="5cb447d1fc4c3308e2c6531e45bcadf1", oauth_token="460633205c55d3f1806bcab04174ae09", oauth_timestamp="1334336004", Content-Length=15125120, Content-Type=video/mp4, Content-Range=bytes: 0-10485759/15125120}
Verb: PUT
Complete URL: http://174.129.125.96:8080/upload?ticket_id=5ea64d64547e38e5e3c121852b2d306d
sendVideo on Test video >>> Response
Code: 200
Headers: {null=HTTP/1.1 200 OK, Content-Length=0, Connection=close, Content-Type=text/plain, Server=Vimeo/1.0}
Signing verifyUpload to http://174.129.125.96:8080/upload?ticket_id=5ea64d64547e38e5e3c121852b2d306d request:
Headers: {Content-Length=0, Content-Range=bytes */*}
verifyUpload to http://174.129.125.96:8080/upload?ticket_id=5ea64d64547e38e5e3c121852b2d306d >>> Request
Headers: {Authorization=OAuth oauth_signature="FQg8HJe84nrUTdyvMJGM37dpNpI%3D", oauth_version="1.0", oauth_nonce="298157825", oauth_signature_method="HMAC-SHA1", oauth_consumer_key="5cb447d1fc4c3308e2c6531e45bcadf1", oauth_token="460633205c55d3f1806bcab04174ae09", oauth_timestamp="1334336015", Content-Length=0, Content-Range=bytes */*}
Verb: PUT
Complete URL: http://174.129.125.96:8080/upload?ticket_id=5ea64d64547e38e5e3c121852b2d306d
verifyUpload to http://174.129.125.96:8080/upload?ticket_id=5ea64d64547e38e5e3c121852b2d306d >>> Response
Code: 308
Headers: {null=HTTP/1.1 308 Resume Incomplete, Range=bytes=0-10485759, Content-Length=0, Connection=close, Content-Type=text/plain, Server=Vimeo/1.0}
Body:
Bytes Left: 4639360
Signing sendVideo on Test video request:
Headers: {Content-Length=15125120, Content-Type=video/mp4, Content-Range=bytes: 10485760-15125119/15125120}
sendVideo on Test video >>> Request
Headers: {Authorization=OAuth oauth_signature="qspQBu42HVhQ7sDpzKGeu3%2Bn8tM%3D", oauth_version="1.0", oauth_nonce="183131870", oauth_signature_method="HMAC-SHA1", oauth_consumer_key="5cb447d1fc4c3308e2c6531e45bcadf1", oauth_token="460633205c55d3f1806bcab04174ae09", oauth_timestamp="1334336015", Content-Length=15125120, Content-Type=video/mp4, Content-Range=bytes%2010485760-15125119/15125120}
Verb: PUT
Complete URL: http://174.129.125.96:8080/upload?ticket_id=5ea64d64547e38e5e3c121852b2d306d
sendVideo on Test video >>> Response
Code: 200
Headers: {null=HTTP/1.1 200 OK, Content-Length=0, Connection=close, Content-Type=text/plain, Server=Vimeo/1.0}
Signing verifyUpload to http://174.129.125.96:8080/upload?ticket_id=5ea64d64547e38e5e3c121852b2d306d request:
Headers: {Content-Length=0, Content-Range=bytes */*}
verifyUpload to http://174.129.125.96:8080/upload?ticket_id=5ea64d64547e38e5e3c121852b2d306d >>> Request
Headers: {Authorization=OAuth oauth_signature="IdhhhBryzCa5eYqSPKAQfnVFpIg%3D", oauth_version="1.0", oauth_nonce="442087608", oauth_signature_method="HMAC-SHA1", oauth_consumer_key="5cb447d1fc4c3308e2c6531e45bcadf1", oauth_token="460633205c55d3f1806bcab04174ae09", oauth_timestamp="1334336020", Content-Length=0, Content-Range=bytes */*}
Verb: PUT
Complete URL: http://174.129.125.96:8080/upload?ticket_id=5ea64d64547e38e5e3c121852b2d306d
4639359 != 15125120
verifyUpload to http://174.129.125.96:8080/upload?ticket_id=5ea64d64547e38e5e3c121852b2d306d >>> Response
Success is not true!
Code: 308
Headers: {null=HTTP/1.1 308 Resume Incomplete, Range=bytes=0-4639359, Content-Length=0, Connection=close, Content-Type=text/plain, Server=Vimeo/1.0}
Body:
Then the code goes on to complete the upload and set video information (you can see that in my full code).
Edit 2: Tried removing the "%20" from the content-range and received this error making connection. I must use either "bytes%20" or not add "bytes" at all...
Exception in thread "main" org.scribe.exceptions.OAuthException: Problems while creating connection.
at org.scribe.model.Request.send(Request.java:70)
at org.scribe.model.OAuthRequest.send(OAuthRequest.java:12)
at autouploadermodel.VimeoTest.signAndSendToVimeo(VimeoTest.java:282)
at autouploadermodel.VimeoTest.sendVideoBytes(VimeoTest.java:130)
at autouploadermodel.VimeoTest.sendVideo(VimeoTest.java:105)
at autouploadermodel.VimeoTest.main(VimeoTest.java:62)
Caused by: java.io.IOException: Error writing to server
at sun.net.www.protocol.http.HttpURLConnection.writeRequests(HttpURLConnection.java:622)
at sun.net.www.protocol.http.HttpURLConnection.writeRequests(HttpURLConnection.java:634)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1317)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:468)
at org.scribe.model.Response.<init>(Response.java:28)
at org.scribe.model.Request.doSend(Request.java:110)
at org.scribe.model.Request.send(Request.java:62)
... 5 more
Java Result: 1
Edit 1: Updated the code and output. Still need help!
I think your problem could simply be the result of this line:
request.addHeader("Content-Range", "bytes%20" + contentRange);
Try and replace "bytes%20" by simply "bytes "
In your output you see the corresponding header has incorrect content:
Headers: {
Content-Length=15125120,
Content-Type=video/mp4,
Content-Range=bytes%200-10485759/15125120 <-- INCORRECT
}
On the topic of Content-Range...
You're right that an example final block of content should have a range like 14680064-15125119/15125120. That's part of the HTTP 1.1 spec.
Here
String contentRange = Integer.toString(byteNumber + 1);
you start from 1 and not from 0 at the first iteration.
Here
request.addHeader("Content-Length", contentLength);
you put the entire file content length and not the length of the current chunk.
The vimeo API page says:
"The final step is to call vimeo.videos.upload.complete to queue up the video for transcoding. This call will return the video_id, which you can then use in other calls (to set the title, description, privacy, etc.). If you do not call this method, the video will not be processed."
I added this bit of code to the end and got it to work:
request = new OAuthRequest(Verb.PUT, "http://vimeo.com/api/rest/v2");
request.addQuerystringParameter("method", "vimeo.videos.upload.complete");
request.addQuerystringParameter("filename", video.getName());
request.addQuerystringParameter("ticket_id", ticket);
service.signRequest(token, request);
response = request.send();
Check this :
String contentRange="bytes "+lastBytesSend+"-"+ ((totalSize - lastBytesSend)-1)+"/"+totalSize ;
request.addHeader("Content-Range",contentRange);

Categories