PUT request with Retrofit doesn't get through but curl does

Viewed 231

I want to send a PUT request from an Android app with Retrofit to my Flask API, which should update a MongoDB document. Running a curl command with the whole URL works just fine, but nothing happens when I run the retrofit function.

Looking at the logs from the server, I can see all my GET requests, but not my PUT request.

flask            | [2020-06-18 19:07:16 +0000] [19] [DEBUG] GET /beers/all
flask            | [2020-06-18 19:07:30 +0000] [20] [DEBUG] GET /beers/by_barcode
flask            | [2020-06-18 19:07:30 +0000] [10] [DEBUG] GET /beers/by_id

When I use this curl command

curl -X PUT 'http://0.0.0.0:5000/users/add_scanned?user_id=test2&beer_id=test2C&beer_name=test2&img_link=http://test2'

everything works as expected, and I see these logs:

flask            | [2020-06-18 19:26:49 +0000] [11] [DEBUG] PUT /users/add_scanned
flask            | [2020-06-18 19:26:49 +0000] [11] [INFO] Adding beer scanned

Mongo doesn't log anything weird. Another PUT request with Retrofit is working like a charm.

flask            | [2020-06-18 19:10:38 +0000] [20] [DEBUG] PUT /beers/update_barcode

Here is my code.

Android Retrofit API

import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Query


private const val BASE_URL = "http://***.***.**.*:5000/"

private val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .build()

private val retrofit = Retrofit.Builder()
    .addConverterFactory(ScalarsConverterFactory.create())
    .addConverterFactory(MoshiConverterFactory.create(moshi))
    .baseUrl(BASE_URL)
    .build()

interface UserApiService {

    /**
     * Adds a beer to scanned list
     */
    @PUT("users/add_scanned")
    fun addScanned(
        @Query("user_id") userId: String,
        @Query("beer_id") beerId: String,
        @Query("beer_name") beerName: String
        ):
            Call<String>
    }

Flask Blueprint

from flask import Blueprint, jsonify, request, abort
from bson.objectid import ObjectId
from pymongo import MongoClient
from flask import current_app

bp = Blueprint('users', __name__, url_prefix='/users')
client = MongoClient(host="mongo", username="*****", password="*****")
db = client.TeddyMongo
collection = db.users

def to_dict(document):
    document["_id"] = str(document["_id"])
    return document

# When a user is not found
@bp.errorhandler(404)
def user_not_found(e):
    return jsonify(error=str(e)), 404

# Add beer scanned
@bp.route('/add_scanned', methods=['PUT'])
def add_scanned():
    user_id = request.args.get('user_id')

    scanned = {
        "_id" : request.args.get('beer_id'),
        "name" : request.args.get('beer_name')
    }

    if request.args.get('img_link'):
        scanned["img_link"] = request.args.get('img_link')

    current_app.logger.info("Adding beer scanned")

    collection.update_one({"_id":user_id}, { "$addToSet" : {"scanned" : scanned}}, upsert=True)

    return "Beer added to scanned"
1 Answers

I was coding an OkHttpRequest call to replace the malfunctioning Retrofit call, and came accross this line:

Android disallows network calls on the main thread, you can only make synchronous calls if you do so on a separate thread or a background service.

What was preventing my call to be made was that it was initiated from the body of another network call. What made that problem hard to track is the complete absence of error logs. Where should I have looked to find hints useful to the debugging of my code?

I eventually changed my code from:

fun onScanFinished(barcode: Long) {
    uiScope.launch {
        BeerApi.retrofitService.searchByBarcode(barcode).enqueue(object : Callback<BeerES> {
            override fun onFailure(call: Call<BeerES>, t: Throwable) {
                Toast.makeText(getApplication(), "Failure: " + t.message, Toast.LENGTH_LONG)
                    .show()
            }
            override fun onResponse(call: Call<BeerES>, response: Response<BeerES>) {
                var beerScanned = response.body()
                if (beerScanned != null) {
                    UserApi.retrofitService.addScanned(
                        FirebaseAuth.getInstance().currentUser!!.uid,
                        beerScanned.beerId,
                        beerScanned.beerInfo.beerName
                    )
                }
            }
        })
    }
}

... to this (extracted the second Api call in an other method with its own uiScope):

fun onScanFinished(barcode: Long) {
    uiScope.launch {
        BeerApi.retrofitService.searchByBarcode(barcode).enqueue(object : Callback<BeerES> {
            override fun onFailure(call: Call<BeerES>, t: Throwable) {
                Toast.makeText(getApplication(), "Failure: " + t.message, Toast.LENGTH_LONG)
                    .show()
            }
            override fun onResponse(call: Call<BeerES>, response: Response<BeerES>) {
                var beerScanned = response.body()
                if (beerScanned != null) {
                    addScanned(beerScanned)
                }
            }
        })
    }
}

private fun addScanned(beerScanned: BeerES) {
    uiScope.launch {
        UserApi.retrofitService.addScanned(
            FirebaseAuth.getInstance().currentUser!!.uid,
            beerScanned.beerId,
            beerScanned.beerInfo.beerName
        ).enqueue(object :  Callback<String> {
            override fun onFailure(call: Call<String>, t: Throwable) {
                 Toast.makeText(getApplication(), "Failure: " + t.message, Toast.LENGTH_LONG)
                    .show()
            }
            override fun onResponse(call: Call<String>, response: Response<String>) {
            }
        })
    }
}

Hope this may help.

Related