Uploading images, getting broken pipeline error

android
java

#1

Hi all,

I am trying to build an app where users can take a picture and upload it with a tweet. For this I am trying to implement the media upload feature (with the media/upload endpoint) to then attach the ID of the uploaded media to a Twitter update. For several reasons I prefer not to use the Tweet Composer for tweeting with images, so I need to get the media upload working. My problem is that whenever I try to upload an image, I get a broken pipeline error and I am not sure what the problem is. I am quite new to Fabric and the REST API, so it is hard to get my head around some problems and I would very much appreciate if someone could help me.

Here are some parts of my code:

The custom image uploading service:

public interface UploadMediaService {

    @Multipart
    @POST("/1.1/media/upload.json")
    void upload(@Part("media") TypedFile file, @Part("additional_owners") String owners, com.twitter.sdk.android.core.Callback<JSONObject> cb);

}

Uploading an image:

File photo = new File(mCurrentPhotoPath);
                            TypedFile typedFile = new TypedFile("application/octet-stream", photo);


                            RestAdapter restAdapter = new RestAdapter.Builder()
                                    .setEndpoint("https://upload.twitter.com")
                                    .setLogLevel(RestAdapter.LogLevel.FULL)
                                    .setRequestInterceptor(new RequestInterceptor() {
                                        @Override
                                        public void intercept(RequestFacade request) {

                                            request.addHeader("Authorization", createSignature(session));

                                        }
                                    })
                                    .build();

                            UploadMediaService service = restAdapter.create(UploadMediaService.class);
                            service.upload(typedFile, null, new Callback<JSONObject>() {
                                @Override
                                public void success(Result<JSONObject> twitterMediaResult) {
                                    try {
                                        Log.v("success", twitterMediaResult.data.getString("media_id_string"));

                                        MyTwitterApiClient myclient = new MyTwitterApiClient(Twitter.getSessionManager().getActiveSession());

                                        StatusWithMediaService mediaService = myclient.getStatusWithMediaService();
                                        mediaService.update("@tkl_testi " + eventtype + ": \n" + comment.getText().toString(), null, false, mypos.latitude, mypos.longitude, null, true, false, twitterMediaResult.data.getString("media_id_string"), new Callback<Tweet>() {
                                            @Override
                                            public void success(Result<Tweet> tweetResult) {
                                                Toast.makeText(getActivity(), "Raportin lähettäminen onnistui.", Toast.LENGTH_SHORT).show();
                                            }

                                            @Override
                                            public void failure(TwitterException e) {
                                                TwitterApiException apiException = (TwitterApiException) e;
                                                Log.e("testapp", apiException.getErrorMessage());
                                                Log.e("testapp", apiException.getMessage());
                                            }
                                        });
                                    } catch (Exception e) {
                                        Log.e("testapp", e.getMessage());
                                    }
                                }

                                @Override
                                public void failure(TwitterException e) {
                                    Log.e("fail", e.getMessage());
                                }
                            });

Creating the signature:

public String createSignature(TwitterSession session) {
        byte[] b = new byte[32];
        new Random().nextBytes(b);
        String randomBytes = null;

        try {
            randomBytes = URLEncoder.encode(String.valueOf(b), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            Log.e("encoding error", e.getMessage());
        }

        long currentTimeInMillis = System.currentTimeMillis();
        TwitterAuthToken authToken = session.getAuthToken();
        String token = session.getAuthToken().token;
        String signature = String.format("oauth_consumer_key=%s&oauth_nonce=%s&oauth_signature_method=HMAC-SHA1&oauth_timestamp=%s&oauth_token=%s&oauth_version=1.0", MainActivity.TWITTER_KEY, randomBytes, currentTimeInMillis, token);
        String finalSignature = null;
        try {
            finalSignature = sha1(signature, MainActivity.TWITTER_SECRET + "&" + authToken.secret);
        } catch (UnsupportedEncodingException e) {
            Log.e("encoding error", e.getMessage());
        } catch (NoSuchAlgorithmException e) {
            Log.e("algorithm error", e.getMessage());
        } catch (InvalidKeyException e) {
            Log.e("key error", e.getMessage());
        }

        String header = String.format("OAuth oauth_consumer_key=\"%s\", oauth_nonce=\"%s\", oauth_signature=\"%s\", " +
                "oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"%s\", oauth_token=\"%s\", oauth_version=\"1.0\")", MainActivity.TWITTER_KEY, randomBytes, finalSignature, currentTimeInMillis, token);

        return header;
    }

    public static String sha1(String s, String keyString) throws
            UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {

        SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");

        mac.init(key);
        byte[] bytes = mac.doFinal(s.getBytes("UTF-8"));

        return Base64.encodeToString(bytes, Base64.URL_SAFE);
    }

Here is what I see in logcat:

Beginning of the HTTP request:

08-07 14:48:02.932  14395-15643/com.example.test.testapp D/Retrofitīš• ---> HTTP POST https://upload.twitter.com/1.1/media/upload.json
08-07 14:48:02.932  14395-15643/com.example.test.testapp D/Retrofitīš• Authorization: OAuth oauth_consumer_key="XXXXX", oauth_nonce="XXXXX", oauth_signature="XXXXX", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1438948082911", oauth_token="XXXXX", oauth_version="1.0")
08-07 14:48:02.932  14395-15643/com.example.test.testapp D/Retrofitīš• Content-Type: multipart/form-data; boundary=2d963c0d-6f81-4b88-8718-7d4100b7f8e3
08-07 14:48:02.934  14395-15643/com.example.test.testapp D/Retrofitīš• Content-Length: 1056730
08-07 14:48:03.086  14395-15643/com.example.test.testapp D/Retrofitīš• --2d963c0d-6f81-4b88-8718-7d4100b7f8e3
    Content-Disposition: form-data; name="media"; filename="JPEG_20150807_144749_1349351950.jpg"
    Content-Type: application/octet-stream
    Content-Length: 1056450
    Content-Transfer-Encoding: binary

Error messages:

08-07 14:48:04.236  14395-15643/com.example.test.testapp D/Retrofitīš• ---- ERROR https://upload.twitter.com/1.1/media/upload.json
08-07 14:48:04.252  14395-15643/com.example.test.testapp D/Retrofitīš• javax.net.ssl.SSLException: Write error: ssl=0x9f822c00: I/O error during system call, Broken pipe
            at com.android.org.conscrypt.NativeCrypto.SSL_write(Native Method)
            at com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream.write(OpenSSLSocketImpl.java:765)
            at com.android.okio.Okio$1.write(Okio.java:70)
            at com.android.okio.RealBufferedSink.emitCompleteSegments(RealBufferedSink.java:116)
            at com.android.okio.RealBufferedSink.write(RealBufferedSink.java:44)
            at com.android.okhttp.internal.http.HttpConnection$FixedLengthSink.write(HttpConnection.java:291)
            at com.android.okio.RealBufferedSink.emitCompleteSegments(RealBufferedSink.java:116)
            at com.android.okio.RealBufferedSink$1.write(RealBufferedSink.java:131)
            at java.io.OutputStream.write(OutputStream.java:82)
            at retrofit.mime.TypedByteArray.writeTo(TypedByteArray.java:66)
            at retrofit.client.UrlConnectionClient.prepareRequest(UrlConnectionClient.java:68)
            at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:37)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:321)
            at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
            at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:278)
            at retrofit.CallbackRunnable.run(CallbackRunnable.java:42)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
            at retrofit.Platform$Android$2$1.run(Platform.java:142)
            at java.lang.Thread.run(Thread.java:818)
08-07 14:48:04.252  14395-15643/com.example.test.testapp D/Retrofitīš• ---- END ERROR
08-07 14:48:04.261  14395-14395/com.example.test.testapp E/failīš• Write error: ssl=0x9f822c00: I/O error during system call, Broken pipe

Can someone tell me what the problem could be? Thanks in advance!


#2

Take a look at class TwitterApiClient, you can use Twitter Retrofit settings (ie: authentication). Unfortunately, main constructor has a package visibility.
You can set your RestAdapter like that :

public CustomTwitterApiClient(TwitterCore twitterCore) {
        super(twitterCore.getSessionManager().getActiveSession());
        Gson gson = (new GsonBuilder()).registerTypeAdapterFactory(new SafeListAdapter()).registerTypeAdapterFactory(new SafeMapAdapter()).create();
        RestAdapter adapter = (new RestAdapter.Builder())
                .setLogLevel(BuildConfig.DEBUG ? RestAdapter.LogLevel.FULL : RestAdapter.LogLevel.NONE)
                .setClient(new AuthenticatedClient(twitterCore.getAuthConfig(), twitterCore.getSessionManager().getActiveSession(), twitterCore.getSSLSocketFactory()))
                .setEndpoint("https://upload.twitter.com").setConverter(new GsonConverter(gson))
                .setExecutors(twitterCore.getFabric().getExecutorService(), new MainThreadExecutor()).build();
        mCustomTwitterService = adapter.create(CustomTwitterService.class);
    }

    public CustomTwitterService getCustomService() {
        return mCustomTwitterService;
    }

I hope it helps.


#3

Twitter Kit 1.5 added support for the media upload endpoint and statuses update (i.e. tweeting) with media ids. https://docs.fabric.io/android/changelog.html#id49

MediaService ms = TwitterCore.getInstance().getApiClient(session).getMediaService();
ms.upload(typedFile, null, null)

See the StatusesService and MediaService in the Javadocs https://docs.fabric.io/javadocs/twitter-core/1.5.0/index.html. Retrofit’s Javadocs provide more info about TypedFile if needed http://square.github.io/retrofit/javadoc/index.html?retrofit/mime/TypedFile.html


#4

Thank you! With the new version of Twitter Kit and the Media Service I could tweet with images easily. It was time these endpoints became supported :slight_smile: