छवि सहित रेट्रोफिट 2.0 का उपयोग करके POST मल्टीपार्ट फॉर्म डेटा


148

मैं Retrofit 2.0 का उपयोग कर सर्वर पर HTTP POST करने की कोशिश कर रहा हूं

MediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain");
MediaType MEDIA_TYPE_IMAGE = MediaType.parse("image/*");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    imageBitmap.compress(Bitmap.CompressFormat.JPEG,90,byteArrayOutputStream);
profilePictureByte = byteArrayOutputStream.toByteArray();

Call<APIResults> call = ServiceAPI.updateProfile(
        RequestBody.create(MEDIA_TYPE_TEXT, emailString),
        RequestBody.create(MEDIA_TYPE_IMAGE, profilePictureByte));

call.enqueue();

सर्वर यह कहते हुए एक त्रुटि देता है कि फाइल वैध नहीं है।

यह अजीब है क्योंकि मैंने उसी फ़ाइल को iOS (अन्य लाइब्रेरी का उपयोग करके) पर उसी प्रारूप के साथ अपलोड करने की कोशिश की है, लेकिन यह सफलतापूर्वक अपलोड करता है।

मैं सोच रहा हूँ कि Retrofit 2.0 का उपयोग करके छवि अपलोड करने का उचित तरीका क्या है ?

अपलोड करने से पहले क्या मुझे इसे डिस्क पर सहेजना चाहिए?

पुनश्च: मैंने अन्य मल्टीपार्ट अनुरोध के लिए रेट्रोफिट का उपयोग किया है जिसमें छवि शामिल नहीं है और वे सफलतापूर्वक पूरा हो गए हैं। समस्या तब है जब मैं शरीर को एक बाइट शामिल करने की कोशिश कर रहा हूं।



जवाबों:


180

मैं 1.9 और 2.0 दोनों में समाधान पर प्रकाश डाल रहा हूं क्योंकि यह कुछ के लिए उपयोगी है

में 1.9 , मुझे लगता है कि बेहतर समाधान डिस्क पर फ़ाइल सहेजने और तरह Typed फ़ाइल के रूप में उपयोग करने के लिए है:

रेट्रोफिट 1.9

(मुझे आपके सर्वर-साइड कार्यान्वयन के बारे में पता नहीं है) इसके समान एक एपीआई इंटरफ़ेस विधि है

@POST("/en/Api/Results/UploadFile")
void UploadFile(@Part("file") TypedFile file,
                @Part("folder") String folder,
                Callback<Response> callback);

और इसका उपयोग करें

TypedFile file = new TypedFile("multipart/form-data",
                                       new File(path));

RetroFit 2 के लिए निम्न विधि का उपयोग करें

रेट्रोफिट 2.0 (यह रेट्रोफिट 2 में एक समस्या के लिए एक समाधान था जो अब तय हो गया है, सही विधि के लिए jimmy0251 के उत्तर को देखें )

एपीआई इंटरफ़ेस:

public interface ApiInterface {

    @Multipart
    @POST("/api/Accounts/editaccount")
    Call<User> editUser(@Header("Authorization") String authorization,
                        @Part("file\"; filename=\"pp.png\" ") RequestBody file,
                        @Part("FirstName") RequestBody fname,
                        @Part("Id") RequestBody id);
}

इसका उपयोग करें जैसे:

File file = new File(imageUri.getPath());

RequestBody fbody = RequestBody.create(MediaType.parse("image/*"),
                                       file);

RequestBody name = RequestBody.create(MediaType.parse("text/plain"),
                                      firstNameField.getText()
                                                    .toString());

RequestBody id = RequestBody.create(MediaType.parse("text/plain"),
                                    AZUtils.getUserId(this));

Call<User> call = client.editUser(AZUtils.getToken(this),
                                  fbody,
                                  name,
                                  id);

call.enqueue(new Callback<User>() {

    @Override
    public void onResponse(retrofit.Response<User> response,
                           Retrofit retrofit) {

        AZUtils.printObject(response.body());
    }

    @Override
    public void onFailure(Throwable t) {

        t.printStackTrace();
    }
});

5
हां, मुझे लगता है कि यह एक मुद्दा है ( github.com/square/retrofit/issues/1063 ) रेट्रोफिट 2.0 के साथ, आप 1.9 के साथ रहना चाहते हैं
insomniac

2
मेरा संपादन देखें, मैंने अभी तक इसकी कोशिश नहीं की है, आपका स्वागत है
insomniac

1
मैंने डे रेट्रोफिट 2.0 उदाहरण का उपयोग करते हुए सफलतापूर्वक एक छवि अपलोड की है।
jerogaren

3
@Bhargav आप इंटरफ़ेस को बदल सकते हैं @Multipart @POST("/api/Accounts/editaccount") Call<User> editUser(@PartMap Map<String, RequestBody> params);और जब आपके पास फ़ाइल होगी: Map<String, RequestBody> map = new HashMap<>(); RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file); map.put("file\"; filename=\"" + file.getName(), fileBody);
insomniac

2
@insomniac हां मुझे इसके बारे में पता चला, मैं भी इस्तेमाल कर सकता हूंMultiPartBody.Part
भार्गव

177

किसी भी हैक के बिना, रिट्रोफिट 2 के साथ अपने नाम के साथ एक फ़ाइल अपलोड करने का एक सही तरीका है :

API इंटरफ़ेस परिभाषित करें:

@Multipart
@POST("uploadAttachment")
Call<MyResponse> uploadAttachment(@Part MultipartBody.Part filePart); 
                                   // You can add other parameters too

फाइल इस तरह अपलोड करें:

File file = // initialize file here

MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), RequestBody.create(MediaType.parse("image/*"), file));

Call<MyResponse> call = api.uploadAttachment(filePart);

यह केवल फ़ाइल अपलोडिंग को प्रदर्शित करता है, आप @Partएनोटेशन के साथ उसी विधि में अन्य पैरामीटर भी जोड़ सकते हैं ।


2
हम MultipartBody.Part का उपयोग करके कई फाइलें कैसे भेज सकते हैं?
प्रवीण शर्मा

आप MultipartBody.Partएक ही एपीआई में कई तर्कों का उपयोग कर सकते हैं ।
jimmy0251 8:20 बजे

मुझे कुंजी के रूप में "छवि []" के साथ छवियों का संग्रह भेजने की आवश्यकता है। मैंने कोशिश की @Part("images[]") List<MultipartBody.Part> imagesलेकिन यह त्रुटि देता है कि@Part parameters using the MultipartBody.Part must not include a part name
प्रवीण शर्मा

आप का उपयोग करना चाहिए @Body MultipartBody multipartBodyऔर MultipartBody.Builderचित्रों के संग्रह को भेजने के लिए।
जिम्मी ०२५१ १०'१६ को ११:०६

2
मैं कैसे उत्परिवर्तित करने के लिए कुंजी जोड़ सकते हैं
andro

23

मैंने अपने रजिस्टर यूजर्स के लिए Retrofit 2.0 का इस्तेमाल किया, मल्टीपार्ट / फॉर्म फाइल इमेज और टेक्स्ट को रजिस्टर अकाउंट से भेजा

मेरे RegisterActivity में, एक AsyncTask का उपयोग करें

//AsyncTask
private class Register extends AsyncTask<String, Void, String> {

    @Override
    protected void onPreExecute() {..}

    @Override
    protected String doInBackground(String... params) {
        new com.tequilasoft.mesasderegalos.dbo.Register().register(txtNombres, selectedImagePath, txtEmail, txtPassword);
        responseMensaje = StaticValues.mensaje ;
        mensajeCodigo = StaticValues.mensajeCodigo;
        return String.valueOf(StaticValues.code);
    }

    @Override
    protected void onPostExecute(String codeResult) {..}

और मेरे Register.java वर्ग में है जहाँ तुल्यकालिक कॉल के साथ रेट्रोफिट का उपयोग करें

import android.util.Log;
import com.tequilasoft.mesasderegalos.interfaces.RegisterService;
import com.tequilasoft.mesasderegalos.utils.StaticValues;
import com.tequilasoft.mesasderegalos.utils.Utilities;
import java.io.File;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call; 
import retrofit2.Response;
/**Created by sam on 2/09/16.*/
public class Register {

public void register(String nombres, String selectedImagePath, String email, String password){

    try {
        // create upload service client
        RegisterService service = ServiceGenerator.createUser(RegisterService.class);

        // add another part within the multipart request
        RequestBody requestEmail =
                RequestBody.create(
                        MediaType.parse("multipart/form-data"), email);
        // add another part within the multipart request
        RequestBody requestPassword =
                RequestBody.create(
                        MediaType.parse("multipart/form-data"), password);
        // add another part within the multipart request
        RequestBody requestNombres =
                RequestBody.create(
                        MediaType.parse("multipart/form-data"), nombres);

        MultipartBody.Part imagenPerfil = null;
        if(selectedImagePath!=null){
            File file = new File(selectedImagePath);
            Log.i("Register","Nombre del archivo "+file.getName());
            // create RequestBody instance from file
            RequestBody requestFile =
                    RequestBody.create(MediaType.parse("multipart/form-data"), file);
            // MultipartBody.Part is used to send also the actual file name
            imagenPerfil = MultipartBody.Part.createFormData("imagenPerfil", file.getName(), requestFile);
        }

        // finally, execute the request
        Call<ResponseBody> call = service.registerUser(imagenPerfil, requestEmail,requestPassword,requestNombres);
        Response<ResponseBody> bodyResponse = call.execute();
        StaticValues.code  = bodyResponse.code();
        StaticValues.mensaje  = bodyResponse.message();
        ResponseBody errorBody = bodyResponse.errorBody();
        StaticValues.mensajeCodigo  = errorBody==null
                ?null
                :Utilities.mensajeCodigoDeLaRespuestaJSON(bodyResponse.errorBody().byteStream());
        Log.i("Register","Code "+StaticValues.code);
        Log.i("Register","mensaje "+StaticValues.mensaje);
        Log.i("Register","mensajeCodigo "+StaticValues.mensaje);
    }
    catch (Exception e){
        e.printStackTrace();
    }
}
}

RegisterService के इंटरफेस में

public interface RegisterService {
@Multipart
@POST(StaticValues.REGISTER)
Call<ResponseBody> registerUser(@Part MultipartBody.Part image,
                                @Part("email") RequestBody email,
                                @Part("password") RequestBody password,
                                @Part("nombre") RequestBody nombre
);
}

यूटिलिटीज पार्स के लिए inr InputStream प्रतिक्रिया

public class Utilities {
public static String mensajeCodigoDeLaRespuestaJSON(InputStream inputStream){
    String mensajeCodigo = null;
    try {
        BufferedReader reader = new BufferedReader(
                new InputStreamReader(
                    inputStream, "iso-8859-1"), 8);
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line).append("\n");
        }
        inputStream.close();
        mensajeCodigo = sb.toString();
    } catch (Exception e) {
        Log.e("Buffer Error", "Error converting result " + e.toString());
    }
    return mensajeCodigo;
}
}

16

Retrofit2.0 में छवि फ़ाइल अपलोड करने के लिए अपडेट कोड

public interface ApiInterface {

    @Multipart
    @POST("user/signup")
    Call<UserModelResponse> updateProfilePhotoProcess(@Part("email") RequestBody email,
                                                      @Part("password") RequestBody password,
                                                      @Part("profile_pic\"; filename=\"pp.png")
                                                              RequestBody file);
}

बदलें MediaType.parse("image/*")करने के लिएMediaType.parse("image/jpeg")

RequestBody reqFile = RequestBody.create(MediaType.parse("image/jpeg"),
                                         file);
RequestBody email = RequestBody.create(MediaType.parse("text/plain"),
                                       "upload_test4@gmail.com");
RequestBody password = RequestBody.create(MediaType.parse("text/plain"),
                                          "123456789");

Call<UserModelResponse> call = apiService.updateProfilePhotoProcess(email,
                                                                    password,
                                                                    reqFile);
call.enqueue(new Callback<UserModelResponse>() {

    @Override
    public void onResponse(Call<UserModelResponse> call,
                           Response<UserModelResponse> response) {

        String
                TAG =
                response.body()
                        .toString();

        UserModelResponse userModelResponse = response.body();
        UserModel userModel = userModelResponse.getUserModel();

        Log.d("MainActivity",
              "user image = " + userModel.getProfilePic());

    }

    @Override
    public void onFailure(Call<UserModelResponse> call,
                          Throwable t) {

        Toast.makeText(MainActivity.this,
                       "" + TAG,
                       Toast.LENGTH_LONG)
             .show();

    }
});

मैं ऐसा करने के लिए कई तरह की कोशिश कर रहा था लेकिन मुझे परिणाम नहीं मिला। मैंने अभी यह कहा ("MediaType.parse (" image / * ") को बदलकर MediaType.parse (" image / jpeg ")") जैसा आपने कहा और यह अब काम करता है, बहुत बहुत धन्यवाद।
गुन्नार १

काश मैं तुम्हें एक से अधिक वोट दे सकता, धन्यवाद।
रोहित मौर्य

यदि आपकी एपीआई में एनोटेशन है, @Multipartतो @Partएक नाम की आपूर्ति करें या मल्टीपार्टबॉडी.पार्ट पैरामीटर प्रकार का उपयोग करें।
रोहित

अच्छा समाधान! और @Part ("profile_pic \" में एक और उद्धरण है; फ़ाइल नाम = \ "pp.png \" ", यह shoule हो@Part("profile_pic\"; filename=\"pp.png "
निंजा

15

@Insomniac द्वारा दिए गए उत्तर को जोड़ना । आप के लिए एक Mapपैरामीटर बना सकते हैंRequestBody छवि शामिल ।

इंटरफ़ेस के लिए कोड

public interface ApiInterface {
@Multipart
@POST("/api/Accounts/editaccount")
Call<User> editUser (@Header("Authorization") String authorization, @PartMap Map<String, RequestBody> map);
}

जावा वर्ग के लिए कोड

File file = new File(imageUri.getPath());
RequestBody fbody = RequestBody.create(MediaType.parse("image/*"), file);
RequestBody name = RequestBody.create(MediaType.parse("text/plain"), firstNameField.getText().toString());
RequestBody id = RequestBody.create(MediaType.parse("text/plain"), AZUtils.getUserId(this));

Map<String, RequestBody> map = new HashMap<>();
map.put("file\"; filename=\"pp.png\" ", fbody);
map.put("FirstName", name);
map.put("Id", id);
Call<User> call = client.editUser(AZUtils.getToken(this), map);
call.enqueue(new Callback<User>() {
@Override
public void onResponse(retrofit.Response<User> response, Retrofit retrofit) 
{
    AZUtils.printObject(response.body());
}

@Override
public void onFailure(Throwable t) {
    t.printStackTrace();
 }
});

मैं 2 स्ट्रिंग्स के साथ कई फाइलें कैसे अपलोड कर सकता हूं?
जे डांगर

क्या आपके लिए stackoverflow.com/questions/60428238/…
रंजीत

14

तो अपने कार्य को प्राप्त करने का यह बहुत ही सरल तरीका है। आपको नीचे दिए गए चरण का अनुसरण करने की आवश्यकता है: -

1. पहला कदम

public interface APIService {  
    @Multipart
    @POST("upload")
    Call<ResponseBody> upload(
        @Part("item") RequestBody description,
        @Part("imageNumber") RequestBody description,
        @Part MultipartBody.Part imageFile
    );
}

आपको संपूर्ण कॉल करने की आवश्यकता है @Multipart requestitemऔर image numberसिर्फ स्ट्रिंग बॉडी है जो अंदर लपेटी जाती है RequestBody। हम इसका उपयोग करते हैं MultipartBody.Part classजो हमें अनुरोध के साथ बाइनरी फ़ाइल डेटा के अलावा वास्तविक फ़ाइल नाम भेजने की अनुमति देता है

2. दूसरा चरण

  File file = (File) params[0];
  RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);

  MultipartBody.Part body =MultipartBody.Part.createFormData("Image", file.getName(), requestBody);

  RequestBody ItemId = RequestBody.create(okhttp3.MultipartBody.FORM, "22");
  RequestBody ImageNumber = RequestBody.create(okhttp3.MultipartBody.FORM,"1");
  final Call<UploadImageResponse> request = apiService.uploadItemImage(body, ItemId,ImageNumber);

अब आपके पास है image pathऔर आपको इसे बदलने की आवश्यकता है । fileअब विधि का उपयोग करने fileमें परिवर्तित करें । अब आप अपने बदलने की आवश्यकता में विधि का उपयोगRequestBodyRequestBody.create(MediaType.parse("multipart/form-data"), file)RequestBody requestFileMultipartBody.PartMultipartBody.Part.createFormData("Image", file.getName(), requestBody);

ImageNumberऔर ItemIdमेरा एक और डेटा है जो मुझे सर्वर पर भेजने की आवश्यकता है इसलिए मैं भी दोनों चीजों को बना रहा हूं RequestBody

अधिक जानकारी के लिए


3

रेट्रोफ़िट का उपयोग करके फ़ाइलें अपलोड करना काफी आसान है, आपको अपने एपीआई इंटरफ़ेस को बनाने की आवश्यकता है

public interface Api {

    String BASE_URL = "http://192.168.43.124/ImageUploadApi/";


    @Multipart
    @POST("yourapipath")
    Call<MyResponse> uploadImage(@Part("image\"; filename=\"myfile.jpg\" ") RequestBody file, @Part("desc") RequestBody desc);

}

उपरोक्त कोड छवि में कुंजी नाम है इसलिए यदि आप php का उपयोग कर रहे हैं तो आप इसे प्राप्त करने के लिए $ _FILES ['छवि'] ['tmp_name'] लिखेंगे । और फ़ाइल नाम = "myfile.jpg" आपकी फ़ाइल का नाम है जिसे अनुरोध के साथ भेजा जा रहा है।

अब फ़ाइल को अपलोड करने के लिए आपको एक विधि की आवश्यकता है जो आपको उरी से निरपेक्ष पथ प्रदान करेगी।

private String getRealPathFromURI(Uri contentUri) {
    String[] proj = {MediaStore.Images.Media.DATA};
    CursorLoader loader = new CursorLoader(this, contentUri, proj, null, null, null);
    Cursor cursor = loader.loadInBackground();
    int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
    cursor.moveToFirst();
    String result = cursor.getString(column_index);
    cursor.close();
    return result;
}

अब आप अपनी फ़ाइल अपलोड करने के लिए नीचे दिए गए कोड का उपयोग कर सकते हैं।

 private void uploadFile(Uri fileUri, String desc) {

        //creating a file
        File file = new File(getRealPathFromURI(fileUri));

        //creating request body for file
        RequestBody requestFile = RequestBody.create(MediaType.parse(getContentResolver().getType(fileUri)), file);
        RequestBody descBody = RequestBody.create(MediaType.parse("text/plain"), desc);

        //The gson builder
        Gson gson = new GsonBuilder()
                .setLenient()
                .create();


        //creating retrofit object
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Api.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

        //creating our api 
        Api api = retrofit.create(Api.class);

        //creating a call and calling the upload image method 
        Call<MyResponse> call = api.uploadImage(requestFile, descBody);

        //finally performing the call 
        call.enqueue(new Callback<MyResponse>() {
            @Override
            public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
                if (!response.body().error) {
                    Toast.makeText(getApplicationContext(), "File Uploaded Successfully...", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(getApplicationContext(), "Some error occurred...", Toast.LENGTH_LONG).show();
                }
            }

            @Override
            public void onFailure(Call<MyResponse> call, Throwable t) {
                Toast.makeText(getApplicationContext(), t.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
    }

अधिक विस्तृत विवरण के लिए आप इस रेट्रोफिट अपलोड फाइल ट्यूटोरियल पर जा सकते हैं


यह एक हैक है, यह थोड़ी देर के लिए रेट्रोफिट 2.0 में तय किया गया है। नीचे jimmy0251 उत्तर देखें।
मैट वोल्फ

1

के अद्यतन के साथ कोटलिन संस्करण RequestBody.create:

रेट्रोफिट इंटरफ़ेस

@Multipart
@POST("uploadPhoto")
fun uploadFile(@Part file: MultipartBody.Part): Call<FileResponse>

और अपलोड करने के लिए

fun uploadFile(fileUrl: String){
    val file = File(fileUrl)
    val fileUploadService = RetrofitClientInstance.retrofitInstance.create(FileUploadService::class.java)
    val requestBody = file.asRequestBody(file.extension.toMediaTypeOrNull())
    val filePart = MultipartBody.Part.createFormData(
        "blob",file.name,requestBody
    )
    val call = fileUploadService.uploadFile(filePart)

    call.enqueue(object: Callback<FileResponse>{
        override fun onFailure(call: Call<FileResponse>, t: Throwable) {
            Log.d(TAG,"Fckd")
        }

        override fun onResponse(call: Call<FileResponse>, response: Response<FileResponse>) {
            Log.d(TAG,"success"+response.toString()+" "+response.body().toString()+"  "+response.body()?.status)
        }

    })
}

@ Jimmy0251 के लिए धन्यवाद


0

फ़ंक्शन नाम में कई मापदंडों का उपयोग न करें बस सरल कुछ args सम्मेलन के साथ जाएं जो कोड की पठनीयता में वृद्धि करेगा , इसके लिए आप इस तरह कर सकते हैं -

// MultipartBody.Part.createFormData("partName", data)
Call<SomReponse> methodName(@Part MultiPartBody.Part part);
// RequestBody.create(MediaType.get("text/plain"), data)
Call<SomReponse> methodName(@Part(value = "partName") RequestBody part); 
/* for single use or you can use by Part name with Request body */

// add multiple list of part as abstraction |ease of readability|
Call<SomReponse> methodName(@Part List<MultiPartBody.Part> parts); 
Call<SomReponse> methodName(@PartMap Map<String, RequestBody> parts);
// this way you will save the abstraction of multiple parts.

कई अपवाद हो सकते हैं जो आप रेट्रोफिट का उपयोग करते समय सामना कर सकते हैं, कोड के रूप में प्रलेखित सभी अपवाद , के पास एक पूर्वाभ्यास हैretrofit2/RequestFactory.java । आप दो कार्य करने में सक्षम हो सकते हैं parseParameterAnnotationऔर parseMethodAnnotationजहां आप फेंकने में अपवाद कर सकते हैं, कृपया इसके माध्यम से जाएं, इससे आपका बहुत समय बचेगा, जैसे कि गोग्लिंग / स्टैकओवरफ़्लो


0

kotlin में इसकी काफी आसान है, जो कि MediaType , asRequestBody और toRequestBody के एक्सटेंशन तरीकों का उपयोग करके यहां एक उदाहरण है:

यहाँ मैं एक पीडीएफ फाइल और मल्टीपार्ट का उपयोग करके एक छवि फ़ाइल के साथ कुछ सामान्य फ़ील्ड पोस्ट कर रहा हूँ

यह रेट्रोफिट का उपयोग करके एपीआई घोषणा है:

    @Multipart
    @POST("api/Lesson/AddNewLesson")
    fun createLesson(
        @Part("userId") userId: RequestBody,
        @Part("LessonTitle") lessonTitle: RequestBody,
        @Part pdf: MultipartBody.Part,
        @Part imageFile: MultipartBody.Part
    ): Maybe<BaseResponse<String>>

और यहाँ है कि वास्तव में इसे कैसे कॉल करें:

api.createLesson(
            userId.toRequestBody("text/plain".toMediaType()),
            lessonTitle.toRequestBody("text/plain".toMediaType()),
            startFromRegister.toString().toRequestBody("text/plain".toMediaType()),
            MultipartBody.Part.createFormData(
                "jpeg",
                imageFile.name,
                imageFile.asRequestBody("image/*".toMediaType())
            ),
            MultipartBody.Part.createFormData(
                "pdf",
                pdfFile.name,
                pdfFile.asRequestBody("application/pdf".toMediaType())
            )
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.