Twitter POST problems using api 1.1

2019-02-14 15:35发布

We've just changed to Twitter api 1.1, and now Tweeting doesn't work & returns an error "The remote server returned an error: (400) Bad Request." Researching on SO about this suggests that it's something to do with authentication, but we are sending the accessToken & secret which we've just got from the login page. It all worked fine with api 1.0. The code is -

    public void Tweet(Action<string> response, string message)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("POST&");
        sb.Append(Uri.EscapeDataString(_postUrl));
        sb.Append("&");

        string oauthNonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));
        string timeStamp = MakeTimestamp();

        var dict = new SortedDictionary<string, string>
        {
            { "oauth_consumer_key", _oAuthConfig.ConsumerKey },
            { "oauth_nonce", oauthNonce },
            { "oauth_signature_method", "HMAC-SHA1" },
            { "oauth_timestamp", timeStamp },
            { "oauth_token", _accessToken },
            { "oauth_version", "1.0" },
        };

        foreach (var keyValuePair in dict)
        {
            sb.Append(Uri.EscapeDataString(string.Format("{0}={1}&", keyValuePair.Key, keyValuePair.Value)));
        }

        string encodedMessage = EscapeAdditionalChars(Uri.EscapeDataString(message));
        sb.Append(Uri.EscapeDataString("status=" + encodedMessage));

        string signatureBaseString = sb.ToString();


        // create the signature

        string signatureKey = Uri.EscapeDataString(_oAuthConfig.ConsumerSecret) + "&" + Uri.EscapeDataString(_accessTokenSecret);

        var hmacsha1 = new HMACSHA1(new ASCIIEncoding().GetBytes(signatureKey));

        string signatureString = Convert.ToBase64String(hmacsha1.ComputeHash(new ASCIIEncoding().GetBytes(signatureBaseString)));


        // create the headers

        string authorizationHeaderParams = String.Empty;

        authorizationHeaderParams += "OAuth ";
        authorizationHeaderParams += "oauth_consumer_key=\"" + _oAuthConfig.ConsumerKey + "\", ";
        authorizationHeaderParams += "oauth_nonce=\"" + oauthNonce + "\", ";
        authorizationHeaderParams += "oauth_signature=\"" + Uri.EscapeDataString(signatureString) + "\", ";
        authorizationHeaderParams += "oauth_signature_method=\"" + "HMAC-SHA1" + "\", ";
        authorizationHeaderParams += "oauth_timestamp=\"" + timeStamp + "\", ";
        authorizationHeaderParams += "oauth_token=\"" + _accessToken + "\", ";
        authorizationHeaderParams += "oauth_version=\"" + "1.0" + "\"";

        string messageToPost = EscapeAdditionalChars(SpacesToPlusSigns(message));


        // initialise the WebClient

        WebClient client = new WebClient();

        client.Headers [HttpRequestHeader.Authorization] = authorizationHeaderParams;

        client.UploadDataCompleted += (s, eArgs) =>
        {
            if (eArgs.Error == null)
                response(DefaultSuccessMessage());
            else
                response(eArgs.Error.Message);
        };

        try
        {
            Uri uri = new Uri(_postUrl);
            try
            {
                client.UploadDataAsync(uri, "POST", Encoding.UTF8.GetBytes("status=" + messageToPost));
            }
            catch (WebException e)
            {
                Log.Info("TwitterService->Tweet web error: " + e.Message);
                response(DefaultErrorMessage());
            }
            catch (Exception e)
            {
                // Can happen if we had already favorited this status
                Log.Info("TwitterService->Tweet error: " + e.Message);
                response(DefaultErrorMessage());
            }
        }
        catch (WebException e)
        {
            Log.Info("TwitterService->Tweet web error 2: " + e.Message);
            response(DefaultErrorMessage());
        }
        catch (Exception e)
        {
            Log.Info("TwitterService->Tweet error 2: " + e.Message);
            response(DefaultErrorMessage());
        }
    }

Basically, I'd like to be able to Tweet without using any 3rd party libraries such as Twitterizer (even TweetStation seems to be broken with api 1.1) - surely it can't be that difficult!

Any help much appreciated, as it feels a bit like a brick wall at the moment - I'm also fairly new to c#, which doesn't help...

Edited to show code which wasn't clear previously.

4条回答
趁早两清
2楼-- · 2019-02-14 15:55

I had exactly the same problem:

This is exactly what you need to do here:

Authenticate and request a user's timeline with Twitter API 1.1 oAuth

I have created a project for this at : https://github.com/andyhutch77/oAuthTwitterTimeline

It also includes an MVC, Web app and console demo.

查看更多
ら.Afraid
3楼-- · 2019-02-14 15:56

I ran into this problem, or at least one striking similiar (from my noob perspective), recently for an app I am building. What seemed to solve it for me (after looking at the tool at dev.twitter.com) was simply to get rid of the quotes around the parameter names, so that (in your case):

I notice that you do in fact not have quotes around your parameter names. However, it confuses me that you send authentication details twice (hence my wrongheaded post.) It works for me without doing this, and I googled it briefly and found: https://dev.twitter.com/discussions/12322#comment-27120, which confirms this can be a problem generating an Authetication Error.

查看更多
走好不送
4楼-- · 2019-02-14 16:07

Finally found the solution, as usual with most of these things, it was pretty simple. Code below -

    public void Tweet(Action<string> response, string message)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat ("status={0}", PercentEncode(message));

        string content = sb.ToString();


        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_postUrl);

        request.Headers.Add("Authorization", AuthorizeRequest(_accessToken, _accessTokenSecret, "POST", new Uri(_postUrl), content));
        request.ContentType = "application/x-www-form-urlencoded";
        request.ServicePoint.Expect100Continue = false;
        request.Method = "POST";


        try
        {
            try
            {
                using (Stream stream = request.GetRequestStream())
                {
                    Byte[] streamContent = Encoding.UTF8.GetBytes("status=" + PercentEncode(message));
                    stream.Write(streamContent, 0, streamContent.Length);
                }


                HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse();

                string contents = "";
                using (Stream stream = webResponse.GetResponseStream())
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        contents = reader.ReadToEnd();
                    }

                Console.WriteLine("Twitter response: " + contents);

                response(DefaultSuccessMessage());

            }
            catch (WebException e)
            {
                Log.Info("TwitterService->Tweet web error: " + e.Message);
                response(DefaultErrorMessage());
            }
            catch (Exception e)
            {
                // Can happen if we had already favorited this status
                Log.Info("TwitterService->Tweet error: " + e.Message);
                response(DefaultErrorMessage());
            }
        }
        catch (WebException e)
        {
            Log.Info("TwitterService->Tweet web error 2: " + e.Message);
            response(DefaultErrorMessage());
        }
        catch (Exception e)
        {
            Log.Info("TwitterService->Tweet error 2: " + e.Message);
            response(DefaultErrorMessage());
        }
    }


    private string AuthorizeRequest(string oauthToken, string oauthTokenSecret, string method, Uri uri, string data)
    {
        string oauthNonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));

        var headers = new Dictionary<string, string>()
        {
            { "oauth_consumer_key", _oAuthConfig.ConsumerKey },
            { "oauth_nonce", oauthNonce },
            { "oauth_signature_method", "HMAC-SHA1" },
            { "oauth_timestamp", MakeTimestamp() },
            { "oauth_token", oauthToken },
            { "oauth_verifier", PercentEncode(_authorizationVerifier) },
            { "oauth_version", "1.0A" }
        };
        var signatureHeaders = new Dictionary<string,string>(headers);

        // Add the data and URL query string to the copy of the headers for computing the signature
        if (data != null && data != "")
        {
            var parsed = HttpUtility.ParseQueryString(data);
            foreach (string k in parsed.Keys)
            {
                signatureHeaders.Add(k, PercentEncode(parsed [k]));
            }
        }

        var nvc = HttpUtility.ParseQueryString(uri.Query);
        foreach (string key in nvc)
        {
            if (key != null)
                signatureHeaders.Add(key, PercentEncode(nvc [key]));
        }

        string signature = MakeSignature (method, uri.GetLeftPart(UriPartial.Path), signatureHeaders);
        string compositeSigningKey = MakeSigningKey(_oAuthConfig.ConsumerSecret, oauthTokenSecret);
        string oauth_signature = MakeOAuthSignature(compositeSigningKey, signature);

        headers.Add ("oauth_signature", PercentEncode(oauth_signature));


        return HeadersToOAuth(headers);
    }


    private static string PercentEncode (string s)
    {
        var sb = new StringBuilder ();

        foreach (byte c in Encoding.UTF8.GetBytes (s))
        {
            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~')
                sb.Append ((char) c);
            else
            {
                sb.AppendFormat ("%{0:X2}", c);
            }
        }
        return sb.ToString ();
    }


    private static string MakeTimestamp ()
    {
        return ((long) (DateTime.UtcNow - _unixBaseTime).TotalSeconds).ToString ();
    }

    private static string MakeSignature (string method, string base_uri, Dictionary<string,string> headers)
    {
        var items = from k in headers.Keys orderby k 
            select k + "%3D" + PercentEncode (headers [k]);

        return method + "&" + PercentEncode (base_uri) + "&" + 
            string.Join ("%26", items.ToArray ());
    }

    private static string MakeSigningKey (string consumerSecret, string oauthTokenSecret)
    {
        return PercentEncode (consumerSecret) + "&" + (oauthTokenSecret != null ? PercentEncode (oauthTokenSecret) : "");
    }

    private static string MakeOAuthSignature (string compositeSigningKey, string signatureBase)
    {
        var sha1 = new HMACSHA1 (Encoding.UTF8.GetBytes (compositeSigningKey));

        return Convert.ToBase64String (sha1.ComputeHash (Encoding.UTF8.GetBytes (signatureBase)));
    }

    private static string HeadersToOAuth (Dictionary<string,string> headers)
    {
        return "OAuth " + String.Join (",", (from x in headers.Keys select String.Format ("{0}=\"{1}\"", x, headers [x])).ToArray ());
    }

With Twitter api 1.0, I used a WebClient to post, that doesn't work with api 1.1, and it seems that the reason for this is that you can't set the ContentType or the ServicePoint.Expect100Continue properties - without these set as I've set them, the request is sent back as (401) unauthorized. Nothing to do with encoding problems in the end.

Thanks to others for the various helper methods.

查看更多
相关推荐>>
5楼-- · 2019-02-14 16:15

400 means you are not authenticated. I recommend getting user context.

https://dev.twitter.com/docs/auth/oauth#user-context

查看更多
登录 后发表回答