Flutter не прикрепляет изображение в multipart для post-запроса

#spring #flutter #dart #xmlhttprequest #flutter-web

#spring #flutter #dart #xmlhttprequest #flutter-web

Вопрос:

Привет, я пытаюсь создать очень простое приложение flutter, которое делает фотографию с моего рабочего стола и отправляет ее на мой сервер через HTTP.POST (в виде составного файла). Я использовал dio , чтобы попробовать это, но по какой-то причине мой сервер продолжает указывать это 'image' is not present . Я попытался отладить почтовый код, изображение отображается в журналах (base64 и т.д.). Я пробую это в PostMan и добиваюсь успеха, хотя с flutter я получаю POST http://localhost:8080/event/test 500 высказывание Required request part 'image' is not present .

Это изображение, которое я пытаюсь использовать:

изображение, которое я пытаюсь использовать

Это некоторые из данных, которые я проверил на то, что они являются нулевыми или недействительными, и, похоже, они возвращаются как действительные после создания моего FormData:

 print(formData.fields.last.key);           // image
print(formData.fields.last.value);         // Instance of 'MultipartFile'
print(formData.fields.last.value.length);  // 27
print(formData.fields.last.value.isEmpty); // false
  

Это мой код flutter:

 import 'dart:io';

import 'package:dio/dio.dart';
import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:flutter/material.dart';
import 'package:http_parser/http_parser.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  FilePickerCross filePickerCross;

  String _fileString = '';
  Set<String> lastFiles;
  FileQuotaCross quota = FileQuotaCross(quota: 0, usage: 0);

  @override
  void initState() {
    FilePickerCross.listInternalFiles()
        .then((value) => setState(() => lastFiles = value.toSet()));
    FilePickerCross.quota().then((value) => setState(() => quota = value));
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
          primaryColor: Colors.blueGrey, accentColor: Colors.lightGreen),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: ListView(
          padding: EdgeInsets.all(8),
          children: <Widget>[
            Text(
              'Last files',
              style: Theme.of(context).textTheme.headline5,
            ),
            (lastFiles == null)
                ? Center(
              child: CircularProgressIndicator(),
            )
                : ListView.builder(
              shrinkWrap: true,
              primary: false,
              physics: NeverScrollableScrollPhysics(),
              itemBuilder: (context, index) => ListTile(
                leading: Text('$index.'),
                title: Text(lastFiles.toList()[index]),
                onTap: () async => setFilePicker(
                    await FilePickerCross.fromInternalPath(
                        path: lastFiles.toList()[index])),
              ),
              itemCount: lastFiles.length,
            ),
            RaisedButton(
              onPressed: _selectFile,
              child: Text('Open File...'),
            ),
            (filePickerCross == null)
                ? Text('Open a file first, to save')
                : RaisedButton(
              onPressed: _selectSaveFile,
              child: Text('Save as...'),
            ),
            Text(
              'File system details',
              style: Theme.of(context).textTheme.headline5,
            ),
            Text('Quota: ${(quota.quota / 1e6).round()} MB'),
            Text(
                'Usage: ${(quota.usage / 1e6).round()}; Remaining: ${(quota.remaining / 1e6).round()}'),
            Text('Percentage: ${quota.relative.roundToDouble()}'),
            Text(
              'File details',
              style: Theme.of(context).textTheme.headline5,
            ),
            Text(
                'File path: ${filePickerCross?.path ?? 'unknown'} (Might cause issues on web)n'),
            Text('File length: ${filePickerCross?.length ?? 0}n'),
            Text('File as String: $_fileStringn'),
          ],
        ),
      ),
    );
  }

  void _selectFile() {
    FilePickerCross.importFromStorage()
        .then((filePicker) => setFilePicker(filePicker));
  }

  void _selectSaveFile() {
    filePickerCross.exportToStorage();
  }

  setFilePicker(FilePickerCross filePicker) => setState(() {
    filePickerCross = filePicker;
    filePickerCross.saveToPath(path: filePickerCross.fileName);
    FilePickerCross.quota().then((value) {
      print(value);
      setState(() => quota = value);
    });
    lastFiles.add(filePickerCross.fileName);
    try {
      _fileString = filePickerCross.toString();
    } catch (e) {
      _fileString = 'Not a text file. Showing base64.nn'  
          filePickerCross.toBase64();
          Upload(filePickerCross);
    }
  });

  Future<void> Upload(FilePickerCross file) async {
    Dio dio = new Dio();
    try{
      FormData formData = new FormData.fromMap({"image":
      file.toMultipartFile()});
      print(formData.fields.last.key);
      print(formData.fields.last.value);
      print(formData.fields.last.value.length);
      print(formData.fields.last.value.isEmpty);

      Response response = await dio.post("http://localhost:8080/event/test",data:formData, options: Options(
          headers: {
            "accept" : "*/*",
            // "Authorization" : "TOKEN",
            "Content-Type" : "multipart/form-data",
          }
      ));
      print(response);
      print("d");
    }catch(e){

    }
  }
}
  

и это то, что я устал от Post man:

изображение post man

Наконец, это мой серверный код:

     @PostMapping(value = "/test")
    @ApiOperation(value = "add an event data via the body ")
    @ApiResponses(value = {@ApiResponse(code = 200, message = "OK", response = ResponseModel.class)})
    public ResponseEntity<?> post(@RequestPart("image") MultipartFile image) {
        ResponseModel responseModel;
        System.out.println(image.getName());
        int responseNumber = 0;
        String res = response(responseNumber);

        if (responseNumber != 0) {
            responseModel = new ResponseModel(false, responseNumber, res);
        } else {
            responseModel = new ResponseModel(true, responseNumber, res);
        }
        return new ResponseEntity<>( responseModel, HttpStatus.OK);
    }
  

Комментарии:

1. Пожалуйста, не публикуйте изображения. Вместо этого вы должны опубликовать код и тело запроса. Пожалуйста, отредактируйте его

2. единственным телом запроса являются данные формы, отображаемые на изображении postman

3. Вы пробовали размещать его как изображение, а не как данные из нескольких частей?

4. @JacksonLee что-то вроде json или что-то в этом роде?

5. @JacksonLee я пытаюсь создать сообщение без MultipartFile, вместо этого я использовал строку, и у меня все та же проблема

Ответ №1:

ИТАК, оказывается, вам нужно реализовать web немного иначе, см. Ниже:

   Future<String> makeRequest() async {
    var url = Uri.parse(
        "http://localhost:8080/event/");
    var request = new http.MultipartRequest("POST", url);
    EventResource eventResource = new EventResource();

    //Created and added values to eventResource 

    var body = jsonEncode(eventResource);

    request.files.add(await http.MultipartFile.fromBytes('image', _selectedFile,
        contentType: new MediaType('application', 'octet-stream'),
        filename: name));
    request.files.add(await http.MultipartFile.fromString('body', body,
        contentType: new MediaType('application', 'json'),
        ));

    request.headers['Content-Type'] = "application/json";
    request.headers['Authorization'] = "SOME_JWT";


    request.send().then((response) {
      print(response.statusCode);
    });