Website certificates for mobile devices return different values - android

While working on certificate pinning for our mobile apps, we've come across this issue making http calls. We see that there are differences on what certificates get returned for each mobile platform. The names of the CNs in the certificate chain are the same, but the encoded SHA256 certificates are different. Hoping to shed some light as to why.
We wrote some sample code to analyze the certificates that were getting returned to our mobile platforms. We were expecting the 256 fingerprints to be the same, but we are instead only getting one or two common values.
Note: These are different from what we see in the browsers as well (chrome, safari).
Here is the result of some sample code written to return the peer certificate chain (in SHA256 base64 encoded format) returned from the various platforms:
Android:
2xC1bw6iB/pA4QmHVuJXllJCgjNTJxtEFISGMNlpLlg=: CN=www.google.com
zCTnfLwLKbS9S2sbp+uFz4KZOocFvXxkV06Ce9O5M2w=: CN=GTS CA 1C3,O=Google Trust Services LLC,C=US
hxqRlPTu1bMS/0DITB1SSu0vd4u/8l8TjPgfaAp63Gc=: CN=GTS Root R1,O=Google Trust Services LLC,C=US
iOS:
HX8NrTiisCa9yA43DdmJT+iuiEakuZ9VcKDGKaSCD7E= CN=www.google.com
zCTnfLwLKbS9S2sbp+uFz4KZOocFvXxkV06Ce9O5M2w= CN=GTS CA 1C3
EumkTYs+nSg5q/mGi38Fjyg/I7lBU59PhayJy7/fx5k= CN=GTS Root R1
Android sample code to return the peer certificate chain :
void analyzeCerts()
{
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BASIC); // Change this to enable logging.
OkHttpClient.Builder httpClient = new OkHttpClient.Builder()
.connectTimeout(DateTimeConstants.SECONDS_PER_MINUTE, TimeUnit.SECONDS)
.readTimeout(DateTimeConstants.SECONDS_PER_MINUTE, TimeUnit.SECONDS);
httpClient.addInterceptor(logging);
String url = "https://www.google.com";
String pinUrl = "www.google.com";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(pinUrl, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
OkHttpClient.Builder clientWithPinner = httpClient.certificatePinner(certificatePinner);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(clientWithPinner.build())
.build();
SimpleInterface si = retrofit.create(SimpleInterface.class);
Call<ResponseBody> call = si.getPublicConsentsByFlow("abcd", "efg");
call.enqueue(new Callback<ResponseBody>()
{
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response)
{
Log.i(CERT_APP, "In onResponse" + response.toString());
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t)
{
Log.i(CERT_APP, "In onFailure " + t);
}
});
}
iOS sample code
import Foundation
import CryptoKit
class ServiceRequest: NSObject, URLSessionDelegate {
private let rsa2048Asn1Header: [UInt8] = [
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00
]
enum ServiceRequestMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
}
var session: URLSession!
override init() {
super.init()
session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
self.certPinTestRequest()
}
func certPinTestRequest(){
let request = NSMutableURLRequest(url: URL(string: "https://google.com")!)
request.httpMethod = ServiceRequestMethod.get.rawValue
request.timeoutInterval = 20.0
session.dataTask(with: request as URLRequest) { (data, response, error) -> Void in
}.resume()
}
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if let trust = challenge.protectionSpace.serverTrust, SecTrustGetCertificateCount(trust) > 0 {
for index in 0..<SecTrustGetCertificateCount(trust) {
// Get the public key data for the certificate at the current index of the loop.
if let certificate = SecTrustGetCertificateAtIndex(trust, index),
let publicKey = SecCertificateCopyPublicKey(certificate),
let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil){
let keyHash = hash(data: (publicKeyData as NSData) as Data)
let certSummary = SecCertificateCopySubjectSummary(certificate) ?? NSString(string: "nil")
print("sha256/\(keyHash) CN=\(certSummary)")
}
}
completionHandler(.useCredential, URLCredential(trust: trust))
return
}
completionHandler(.cancelAuthenticationChallenge, nil)
}
private func hash(data: Data) -> String {
var keyWithHeader = Data(rsa2048Asn1Header)
keyWithHeader.append(data)
if #available(iOS 13.0, *) {
return Data(SHA256.hash(data: keyWithHeader)).base64EncodedString()
} else {
// Fallback on earlier versions
}
return ""
}
}

Related

How to get this iOS Http Post Request to work?

I am setting up a payout process for a driver in my app with Firebase Cloud Functions and Paypal. The url to be posted is the url of the actual cloud function in Firebase:
https://us-central1-ryyde-sj.cloudfunctions.net/payout
When trying to send an HTTP Post Request, it doesn't seem to be working. See the payoutRequest() and the Response code below:
payoutRequest()
let email = txtPayoutEmail.text!
let uid = self.uid!
// Prepare URL:
let url = URL(string: "https://us-central1-ryyde-sj.cloudfunctions.net/payout")
guard let requestUrl = url else { fatalError() }
// Prepare URL Request Object:
var request = URLRequest(url: requestUrl)
request.httpMethod = "POST"
// Set HTTP Request Headers
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Your Token", forHTTPHeaderField: "Authorization")
request.setValue("no-cache", forHTTPHeaderField: "cache-control")
print("request = \(request)")
// HTTP Request Parameters which will be sent in HTTP Request Body:
let postString = "uid=\(uid)&email=\(email)"
print("postString = \(postString)")
// Set HTTP Request Body
request.httpBody = postString.data(using: String.Encoding.utf8)
// Perform HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error ) in
print("data: \(String(describing: data))")
print("response: \(String(describing: response))")
print("error: \(String(describing: error))")
if let response = response as? HTTPURLResponse {
// Read all HTTP Response Headers
print("All headers: \(response.allHeaderFields)")
// Read a specific HTTP Response Header by name
if #available(iOS 13.0, *) {
print("Specific header: \(response.value(forHTTPHeaderField: "Content-Type") ?? " header not found")")
} else {
// Fallback on earlier versions
}
}
// Check for Errors
if let error = error {
print("Error took place \(error)")
return
}
// Convert HTTP Response Data to a String
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Response data string: \(dataString)")
}
}
task.resume()
Response
request = https://us-central1-ryyde-sj.cloudfunctions.net/payout
postString = uid=kv8JRVBwAfS1tgD04lNeM9esVzI2&email=myiosapp#me.com
data: Optional(138 bytes)
response: Optional(<NSHTTPURLResponse: 0x6000037d1c20> { URL: https://us-central1-ryyde-sj.cloudfunctions.net/payout } { Status Code: 400, Headers {
"Content-Length" = (
138
);
"Content-Type" = (
"text/html; charset=utf-8"
);
Date = (
"Thu, 17 Sep 2020 01:00:50 GMT"
);
Server = (
"Google Frontend"
);
"alt-svc" = (
"h3-Q050=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000,h3-27=\":443\"; ma=2592000,h3-T051=\":443\"; ma=2592000,h3-T050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\""
);
"content-security-policy" = (
"default-src 'none'"
);
"function-execution-id" = (
cmrwbktlroxl
);
"x-cloud-trace-context" = (
"a85aaacd578e60690581aa64ead13b23;o=1"
);
"x-content-type-options" = (
nosniff
);
"x-powered-by" = (
Express
);
} })
error: nil
All headers: [AnyHashable("content-security-policy"): default-src 'none',
AnyHashable("Date"): Thu, 17 Sep 2020 01:00:50 GMT, AnyHashable("alt-svc"): h3-Q050=":443";
ma=2592000,h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-
T050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443";
ma=2592000; v="46,43", AnyHashable("Content-Type"): text/html; charset=utf-8,
AnyHashable("Content-Length"): 138, AnyHashable("x-cloud-trace-context"):
a85aaacd578e60690581aa64ead13b23;o=1, AnyHashable("Server"): Google Frontend,
AnyHashable("x-powered-by"): Express, AnyHashable("x-content-type-options"): nosniff,
AnyHashable("function-execution-id"): cmrwbktlroxl]
Specific header: text/html; charset=utf-8
Response data string: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Bad Request</pre>
</body>
</html>
If the request is successful, then it would show up in the PayPal Notifications for PayPal Sandbox at the below link, but it isn't.
PayPal developer notifications link
I don't have much experience in PayPal HTTP requests.
I have done the same thing as I am trying to do here but in Android and it works perfectly so I know this should work, other than the Post Request (I tried using examples online to match what i had for the Android app)
Edit
updated payoutRequest():
Code surrounded in ** ** is new code
let email = txtPayoutEmail.text!
let uid = self.uid!
// Prepare URL:
let url = URL(string: "https://us-central1-ryyde-sj.cloudfunctions.net/payout")
guard let requestUrl = url else { fatalError() }
// Prepare URL Request Object:
var request = URLRequest(url: requestUrl)
request.httpMethod = "POST"
// Set HTTP Request Headers
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Your Token", forHTTPHeaderField: "Authorization")
request.setValue("no-cache", forHTTPHeaderField: "cache-control")
print("request = \(request)")
// HTTP Request Parameters which will be sent in HTTP Request Body:
**let body = ["uid": uid, "email": email]**
print("body = \(body)")
// Set HTTP Request Body
**request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: [])**
// Perform HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error ) in
print("data: \(String(describing: data))")
print("response: \(String(describing: response))")
print("error: \(String(describing: error))")
if let response = response as? HTTPURLResponse {
// Read all HTTP Response Headers
print("All headers: \(response.allHeaderFields)")
// Read a specific HTTP Response Header by name
if #available(iOS 13.0, *) {
print("Specific header: \(response.value(forHTTPHeaderField: "Content-Type") ?? " header not found")")
} else {
// Fallback on earlier versions
}
}
// Check for Errors
if let error = error {
print("Error took place \(error)")
return
}
// Convert HTTP Response Data to a String
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Response data string: \(dataString)")
}
}
task.resume()
Response:
request = https://us-central1-ryyde-sj.cloudfunctions.net/payout
body = ["uid": "kv8JRVBwAfS1tgD04lNeM9esVzI2", "email": "driver#ryyde.com"]
data: Optional(0 bytes)
response: Optional(<NSHTTPURLResponse: 0x600001f0d6a0> { URL: https://us-central1-ryyde-sj.cloudfunctions.net/payout } { Status Code: 200, Headers {
"Content-Length" = (
0
);
"Content-Type" = (
"text/html"
);
Date = (
"Thu, 17 Sep 2020 04:41:29 GMT"
);
Server = (
"Google Frontend"
);
"alt-svc" = (
"h3-29=\":443\"; ma=2592000,h3-27=\":443\"; ma=2592000,h3-T051=\":443\"; ma=2592000,h3-T050=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\""
);
"function-execution-id" = (
cmrwtq89fdsr
);
"x-cloud-trace-context" = (
"f3fe884ca8499e7a10c7081ce222876e;o=1"
);
"x-powered-by" = (
Express
);
} })
error: nil
All headers: [AnyHashable("Content-Length"): 0, AnyHashable("x-cloud-trace-context"): f3fe884ca8499e7a10c7081ce222876e;o=1, AnyHashable("Server"): Google Frontend, AnyHashable("x-powered-by"): Express, AnyHashable("function-execution-id"): cmrwtq89fdsr, AnyHashable("alt-svc"): h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43", AnyHashable("Date"): Thu, 17 Sep 2020 04:41:29 GMT, AnyHashable("Content-Type"): text/html]
Specific header: text/html
Response data string:
EDIT 2
When I run my code, I check the function logs in firebase/functions (read from bottom up - seems to go ok with the function activity)
EDIT 3 - Charles Session results
URL https://us-central1-ryyde-sj.cloudfunctions.net Status Sending
request body… Notes Transaction began prior to session being cleared,
body content transmitted before the session clear has not been
captured Response Code 200 Connection established Protocol HTTP/1.1
TLS TLSv1.2 (TLS_AES_128_GCM_SHA256) Protocol TLSv1.2 Session
Resumed Yes Cipher Suite TLS_AES_128_GCM_SHA256 ALPN - Client
Certificates - Server Certificates - Extensions Method CONNECT Kept
Alive No Content-Type Client Address 127.0.0.1:57209 Remote
Address us-central1-ryyde-sj.cloudfunctions.net/216.239.36.54:443
Tags - Connection WebSockets - Timing Size Request 1.77 KB (1,817
bytes) Response 1.35 KB (1,379 bytes)
EDIT 4 - Android code
private void payoutRequest() {
progress = new ProgressDialog(this);
progress.setTitle("Processing your payout ...");
progress.setMessage("Please Wait .....");
progress.setCancelable(false);
progress.show();
// HTTP Request ....
final OkHttpClient client = new OkHttpClient();
// in json - we need variables for the hardcoded uid and Email
JSONObject postData = new JSONObject();
try {
postData.put("uid", FirebaseAuth.getInstance().getCurrentUser().getUid());
postData.put("email", mPayoutEmail.getText().toString());
} catch (JSONException e) {
e.printStackTrace();
}
// Request body ...
RequestBody body = RequestBody.create(MEDIA_TYPE, postData.toString());
// Build Request ...
final Request request = new Request.Builder()
.url("https://us-central1-ryyde-sj.cloudfunctions.net/payout")
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("cache-control", "no-cache")
.addHeader("Authorization", "Your Token")
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
// something went wrong right off the bat
progress.dismiss();
}
#Override
public void onResponse(Call call, Response response) throws IOException {
// response successful ....
// refers to response.status('200') or ('500')
int responseCode = response.code();
if (response.isSuccessful()) {
switch(responseCode) {
case 200:
Snackbar.make(findViewById(R.id.layout),
"Payout Successful!", Snackbar.LENGTH_LONG)
.show();
break;
case 500:
Snackbar.make(findViewById(R.id.layout),
"Error: no payout available", Snackbar
.LENGTH_LONG).show();
break;
default:
Snackbar.make(findViewById(R.id.layout),
"Error: couldn't complete the transaction",
Snackbar.LENGTH_LONG).show();
break;
}
} else {
Snackbar.make(findViewById(R.id.layout),
"Error: couldn't complete the transaction",
Snackbar.LENGTH_LONG).show();
}
progress.dismiss();
}
});
}
try this:
let body = ["uid": uid,
"email": email]
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: [])

Convert OkHttp's Form Data (Android) to Axios(React Native)'s Form Data

I'm recreating a project that was originally designed for Native Android to use React Native. There is an endpoint that is responsible to send a image using Form Data. I tried to convert the OkHttp3's Form Data to Axios's Form Data and I'm getting an error from backend saying that the request fields doesn't match.
My Code so far:
- Native Android(original app):
public RequestResponse<Boolean> functionApi(#NonNull String id, String imageExtension, #NonNull byte[] imageData, #NonNull String anotherData) throws ServerErrorException, IOException, AuthenticationException {
String path = "route/" + id;
Pair<String, String> contentTypeHeader = new Pair<>("Content-Type", "multipart/form-data");
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("anotherData", anotherData)
.addFormDataPart("imageData", id + "." + imageExtension, RequestBody.create(MediaType.parse("image/png"), imageData))
.build();
Response response = MyHttpClient.execute(path, "POST", requestBody, contentTypeHeader);
String body = response.body().string();
RequestResponse<Boolean> r = responseBodyToObject(body, RequestResponse.class);
r.setBody(r.getStatus() != RequestResponse.ERROR);
return r;
}
React Native(new app) version:
export const functionApi = async(id,imageExtension,imageData,anotherData)=>{
try{
let formData = new FormData()
formData.append('anotherData',anotherData)
formData.append('imageData',`data:image/${imageExtension};base64,${imageData}`,`${id}.${imageExtension}`)
//imageData here i tried to use a base64's string
let res = await axios({
url:`${URL_SERVER}/route/${id}`,
method:'POST',
headers:{
'Content-Type':"multipart/form-data"
},
data:formData
})
return res['data']
}catch(err){
return getErrorMessage(err)
}
}
I got a solution that finally worked for me:
export const functionApi = async(id,imageExtension,imageData,anotherData)=>{
try{
let formData = new FormData()
formData.append('anotherData',anotherData)
formData.append('imageData',{
uri: imageData['uri'],
type: 'image/jpg',
name: `${id}.${imageExtension}`,
})
let res = await axios({
url:`${URL_SERVER}/route/${id}`,
method:'POST',
headers:{
'Content-Type':'multipart/form-data'
},
data:formData
})
return res['data']
}catch(err){
return getErrorMessage(err)
}
}

Why does Google ReCaptcha API allow bad secret key and/or bad user token response?

Take a look at the code below. This is server-side. I call the Google ReCaptcha API with bad secret key and bad user token response, as you can see. You know what? It works! More precisely: Google ReCaptcha API doesn't answer with exception (i.e.: my catch isn't reached). Why? This is not the expected behavior, right?
/**
* Verifies a Recaptcha filled by the user in his Android app.
* 1. Success: returns the JSON response
* 2. Failure: throws the error
**/
exports.verifyRecaptcha = functions.https.onCall((data, context) => {
const user_response_token = data.userResponseToken;
if(user_response_token === null || user_response_token === '') {
throw new functions.https.HttpsError('invalid-argument', 'The function must be called with an adequat user response token.');
}
const remote_url = 'https://recaptcha.google.com/recaptcha/api/siteverify';
const secret = '<MY_REAL_SECRET_KEY>'; // Original value: 'https://www.google.com/recaptcha/api/siteverify'; # Moises' value: https://recaptcha.google.com/recaptcha/api/siteverify
var options = {
method: 'POST',
uri: remote_url,
body: {secret: 'Foo', response: 'Bar'},
// body: {secret: secret, response: user_response_token},
json: true
};
return rp(options)
.then(parsedBody => {
return {code: 'Success', message: 'You are actually a human (this msg is for test purposes).'};
})
.catch(error => {
throw new functions.https.HttpsError('unknown', error);
});
});
And below is the Android app code:
final SafetyNetApi.RecaptchaTokenResponse response = task.getResult();
assert response != null;
final String userResponseToken = response.getTokenResult();
if (!userResponseToken.isEmpty()) {
final HashMap<String, String> the_data = new HashMap<>();
the_data.put("userResponseToken", userResponseToken);
FirebaseFunctions.getInstance()
.getHttpsCallable("verifyRecaptcha")
.call(the_data)
.continueWith(new Continuation<HttpsCallableResult, Void>() {
#Override
public Void then(#NonNull final Task<HttpsCallableResult> task) {
if(context.isDestroyed() || context.isFinishing()) {
return null;
}
if(!task.isSuccessful()) {
Exception e = task.getException();
if (e instanceof FirebaseFunctionsException) {
FirebaseFunctionsException ffe = (FirebaseFunctionsException) e;
System.out.println(ffe.getMessage());
}
return null;
}
callback.onAsking();
return null;
}
});
} else {
callback.onFailureUserResponseTokenIsEmpty();
}
The docs suggest that errors such as invalid-input-secret/invalid-input-response will appear in the error-codes field of the response.
This information doesn't necessarily need to be translated into an HTTP error code (which would cause your catch block to execute); in this instance, Google apparently wanted to support multiple simultaneous error messages, and the HTTP response code pertains more to the conduct of the protocol at the HTTP level.
While we're looking at the docs, I should point out that you probably want to refer to the success field before presuming that your user is a human.

Unexpected token # in JSON at position 0 Error on Android Studio using Retrofit2

I have a problem that "Unexpected token # in JSON at position 0".
What I wanna do is FirebaseUser delete using CloudFunction on Firebase.
There is no return but the Error says that "there is unexpected json token #"
This is some codes:
CloudFunctionsService( Interface )
public interface CloudFunctionsService {
#POST("deleteUser")
Call<Void> deleteUser(#Body String uid);
}
FunctionRetrofit ( RetrofitClass )
public class FunctionRetrofit {
private static FunctionRetrofit instance = new FunctionRetrofit();
public static FunctionRetrofit getInstance(){
return instance;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
private CloudFunctionsService cfs = retrofit.create(CloudFunctionsService.class);
public CloudFunctionsService getService(){
return cfs;
}
}
function deleteUser ( on FirebaseCloudFunction )
exports.deleteUser = functions.https.onRequest((req, res) => {
if (req.body.uid === undefined) {
res.status(400).send('No user id defined');
} else {
var userId = req.body.uid;
admin.auth().deleteUser(userId)
.then(function() {
console.log("Successfully deleted user");
})
.catch(error=> {
console.log("Error deleting user: ", error);
});
res.status(200).end();
}
});
Code Executing ( Activity )
result is success but actually not changed anything
Call<Void> res = FunctionRetrofit.getInstance().getService().deleteUser(user.getUid());
res.enqueue(new Callback() {
#Override
public void onResponse(Call call, Response response) {
Log.d("success", "suceess");
}
#Override
public void onFailure(Call call, Throwable t) {
Log.e("Error", t.getMessage().toLowerCase());
}
});
Error
SyntaxError: Unexpected token # in JSON at position 0
at JSON.parse (<anonymous>)
at createStrictSyntaxError (/worker/node_modules/body-parser/lib/types/json.js:157:10)
at parse (/worker/node_modules/body-parser/lib/types/json.js:83:15)
at /worker/node_modules/body-parser/lib/read.js:121:18
at invokeCallback (/worker/node_modules/raw-body/index.js:224:16)
at done (/worker/node_modules/raw-body/index.js:213:7)
at IncomingMessage.onEnd (/worker/node_modules/raw-body/index.js:273:7)
at emitNone (events.js:106:13)
at IncomingMessage.emit (events.js:208:7)
at endReadableNT (_stream_readable.js:1064:12)
As you are using the GsonConverterFactory, I think it's expecting json (or to serialize to JSON) when you use the #Body annotation. As you are passing a raw String value I think this is where it errors.
Please disregard the answer above. The GsonConverterFactory will serialise your own Type to JSON, however you are sending in a raw String value. This will not be serialized so the body of the post for an id of 3 will be "3" - I think the api you are calling for deleteUser is expecting JSON in the body which you are not sending which is why you are getting the error. I would check the docs of the Firebase API call you are making to see what format it expects the post body to be in. It is more likely to be something like:
{
"userId": "3"
}
If this is the case then you would need a class like:
public class User {
private String userId;
public User(String userId){
this.userId = userId;
}
}
You are currently sending a " character as the first character when it's probably expecting a { to signify starting a JSON object

sending file to server by okhttp

I have this node.js code in my server side app:
app.post('/upload',function (req,resp) {
console.log(req);
var email=req.headers['email']
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null,'uploads/')
},
filename: function (req, file, cb) {
cb(null,"asdsad"+".jpg")
}
})
var upload = multer({ storage: storage }).single('propic')
upload(req, resp, function (err) {
if (err) {
throw err
}
resp.setHeader('Content-Type', 'application/json');
resp.send({status:true})
})
})
I want to send a bitmap in client side (Android) to server.
I used the okhttp library and I want to create form-data.
How can I do that?
You must send File to your server instead of bitmap and must use POST method to handle the request to Server side.
and write this codes to send your file into the server:
public class UploadService {
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
public void uploadImage(File image, String imageName) throws IOException {
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart("file", imageName, RequestBody.create(MEDIA_TYPE_PNG, image))
.build();
Request request = new Request.Builder().url("http://localhost:8080/v1/upload")
.post(requestBody).build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
}
}

Categories

Resources