2015-05-20 2 views
1

У нас есть проект, работающий на Undertow & SpringBoot и пытается добавить файлы. Первые попытки были успешными, файлы были привязаны к соответствующим компонентам, используя StandardServletMultipartResolver и настраивая его с помощью application.properties. Однако мы столкнулись с ужасными трудностями, когда дело дошло до Error Handling. Мы нашли «решение», настроив стандартный преобразователь на 100 МБ и используя CommonsMultipartResolver. Затем мы добавили фильтр, как этотЗагрузка файла и допустимая обработка ошибок в Undertow/Wildfly wth Весенняя загрузка

@Bean 
public Filter filter() { 
    return new OncePerRequestFilter() { 
     @Override 
     protected void doFilterInternal(HttpServletRequest request, 
       HttpServletResponse response, FilterChain filterChain) 
       throws ServletException, IOException { 
      try { 
       filterChain.doFilter(request, response); 
      } catch (ServletException e) { 
       if (e.getCause() 
         .getClass() 
         .equals(org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException.class)) { 
        int requestSize = request.getContentLength(); 
        Collection<Part> parts = request.getParts(); 
        List<String> oversizedFields = new LinkedList<>(); 
        long uploadSize = 0; 
        for (Part part : new ArrayList<>(parts)) { 
         if (uploadSize + part.getSize() > MAX_UPLOAD_SIZE) { 
          requestSize -= part.getSize(); 
          oversizedFields.add(part.getName()); 
          request.getParameterMap() 
            .remove(part.getName()); 
          parts.remove(part); 
         } else { 
          uploadSize += part.getSize(); 
         } 
        } 
        request.setAttribute("oversizedFields", oversizedFields); 
        SizeModifyingServletRequestWrapper requestWrapper = new SizeModifyingServletRequestWrapper(
          request, requestSize, uploadSize); 
        filterChain.doFilter(requestWrapper, response); 
       } 
      } 
     } 
    }; 
} 

Requestwrapper:

private static class SizeModifyingServletRequestWrapper extends 
     HttpServletRequestWrapper { 
    private int size; 
    private long sizeLong; 

    public SizeModifyingServletRequestWrapper(HttpServletRequest request, 
      int size, long sizeLong) { 
     super(request); 
     this.size = size; 
     this.sizeLong = sizeLong; 
    } 

    @Override 
    public int getContentLength() { 
     return size; 
    } 

    @Override 
    public long getContentLengthLong() { 
     return sizeLong; 
    } 

    @Override 
    public String getHeader(String name) { 
     if (FileUploadBase.CONTENT_LENGTH.equals(name)) { 
      return Integer.toString(size); 
     } else { 
      return super.getHeader(name); 
     } 
    } 
} 

@Controller -метод затем проверяет наличие больших файлов и добавляет результат к BindingResult, который прекрасно работает, за исключением того, что файлы не привязаны к компоненту. Оказывается, что при попытке разобрать запрос CommonsMultipartResolver выбрасывает MalformedStreamException в ItemInputStream.makeAvailable(), который всегда возвращает сообщение String ended unexpectedly.

Таким образом, мы вернулись к использованию StandardServletMultipartResolver и смогли поймать RuntimeException. Он отлично поддается, однако он не предоставляет абсолютно никаких данных формы, когда даже один файл превышает границы его размера.

Мы абсолютно застопорились, поскольку это неважно, работает ли резольвер лениво или нет. Если у кого есть какие-либо дополнительные идеи, как решить этот вопрос, то можете предложить ответы =)

Далее Код для ссылки:

Выписка из WebAppInitializer

@Bean(name = "multipartResolver") 
public MultipartResolver multipartResolver() { 
    StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); 
    multipartResolver.setResolveLazily(true); 
    return multipartResolver; 
} 

@Bean 
public MultipartConfigElement multipartConfigElement() { 
    MultipartConfigFactory factory = new MultipartConfigFactory(); 
    factory.setMaxFileSize("2MB"); 
    factory.setMaxRequestSize("100MB"); 
    return factory.createMultipartConfig(); 
} 

Выписка из контроллера:

@RequestMapping(method = { RequestMethod.POST, RequestMethod.PUT }) 
public String saveOrganizationDetails(
     @PathVariable(PATH_VARIABLE_ORGANIZATION_ID) String organizationId, 
     @ModelAttribute @Valid Organization organization, 
     BindingResult bindingResult, Model model, 
     RedirectAttributes redirectAttributes, WebRequest request) { 
checkForOversizedFiles(request, bindingResult); 
    Map<String, MultipartFile> files = organization.getStyle().whichFiles(); 
} 

private boolean checkForOversizedFiles(WebRequest request, 
     BindingResult bindingResult) { 
    if (request.getAttribute("oversizedFields", WebRequest.SCOPE_REQUEST) instanceof LinkedList) { 
     @SuppressWarnings("unchecked") 
     LinkedList<String> oversizedFiles = (LinkedList<String>) request 
       .getAttribute("oversizedFields", WebRequest.SCOPE_REQUEST); 
     for (String s : oversizedFiles) { 
      String errorCode = KEY_ORGANIZATION_LOGO_OVERSIZED_FILE + s; 
      bindingResult.rejectValue(s, 
        errorCode); 
     } 
     return true; 
    } else { 
     return false; 
    } 
} 

private void handleUpload(Map<String, MultipartFile> files, 
     OrganizationStyle style, BindingResult result) { 
    for (String filename : files.keySet()) { 
     if (processUpload(files.get(filename), filename)) { 
      style.setLogoFlag(filename); 
     } else { 
      result.reject(KEY_ORGANIZATION_LOGO_UPLOAD_FAILURE); 
     } 
    } 
} 

processUpload() до сих пор не имеет функциональности, поэтому я не включаю его здесь.

Извлечение из формы щиту Bean:

public class OrganizationStyle { 
@Transient 
private MultipartFile logoPdf; 
@Transient 
private MultipartFile logoCustomerArea; 
@Transient 
private MultipartFile logoAssistant; 
@Transient 
private MultipartFile logoIdentityArea; 

<omitting Getters and setters> 

private Map<String, MultipartFile> getAllFiles() { 
    Map<String, MultipartFile> files = new HashMap<>(); 
    files.put("logoPdf", logoPdf); 
    files.put("logoCustomerArea", logoCustomerArea); 
    files.put("logoAssistant", logoAssistant); 
    files.put("logoIdentityArea", logoIdentityArea); 
    return files; 
} 

public Map<String, MultipartFile> whichFiles() { 
    Map<String, MultipartFile> whichFiles = new HashMap<>(); 
    for (String name : getAllFiles().keySet()) { 
     MultipartFile file = getAllFiles().get(name); 
     if (file != null && !file.isEmpty()) { 
      whichFiles.put(name, file); 
     } 
    } 
    return whichFiles; 
} 
} 

Это, как говорится, не весь код, но необходимый код для этой конкретной проблемы. Исключение брошено при загрузке больших файлов или:

(java.io.IOException) java.io.IOException: UT000054: The maximum size 2097152 for an individual file in a multipart request was exceeded 

или упомянутый FileUploadBase.FileSizeLimitExceedeException

И последнее, но не в последнюю очередь, экстракт форм-страницы

<div id="layoutOne" class="panel-collapse collapse"> 
    <div class="panel-body"> 
     <div class="form-group"> 
      <label for="logoPdf" class="control-label" th:text="#{organizationcontext.groups.addmodal.logo.form.label}">LOGO-FORM</label> 
      <input type="file" th:field="*{style.logoPdf}" accept="image/*" /> 
     </div> 
     <div class="form-group"> 
      <label for="logoCustomerArea" class="control-label" th:text="#{organizationcontext.groups.addmodal.logo.customer.label}">LOGO-ORGANIZATION</label> 
      <input type="file" th:field="*{style.logoCustomerArea}" accept="image/*" /> 
     </div> 
     <div class="form-group"> 
      <label for="logoAssistant" class="control-label" th:text="#{organizationcontext.groups.addmodal.logo.assistant.label}">LOGO-ASSISTANT</label> 
      <input type="file" th:field="*{style.logoAssistant}" accept="image/*" /> 
     </div> 
     <div class="form-group"> 
      <label for="logoIdentityArea" class="control-label" th:text="#{organizationcontext.groups.addmodal.logo.id.label}">LOGO-ID</label> 
      <input type="file" th:field="*{style.logoIdentityArea}" accept="image/*" /> 
     </div> 
     <div class="form-group" th:classappend="${#fields.hasErrors('style.cssUrl')}? has-error"> 
      <label for="style.cssUrl" class="control-label" th:text="#{organizationcontext.groups.addmodal.css.external.label}">CSS-EXTERNAL</label> 
      <input th:field="*{style.cssUrl}" class="form-control" type="text" th:placeholder="#{placeholder.css.external}" /> 
     </div> 
     <div class="form-group" th:classappend="${#fields.hasErrors('style.cssCode')}? has-error"> 
      <label for="style.cssCode" class="control-label" th:text="#{organizationcontext.groups.addmodal.css.input.label}">CSS</label> 
      <textarea th:field="*{style.cssCode}" class="form-control" th:placeholder="#{placeholder.css.input}"></textarea> 
     </div> 
    </div> 
</div> 

Если у вас вы должны были понять, что мы уже пробовали несколько возможных решений, большинство из которых были здесь.Прямо сейчас, фильтр не ловит RuntimeException и чеки IOException в качестве причины, а также, размеры больше не устанавливается в пределах application.properties

Любая помощь или предложения вообще были бы очень признательны.

Дополнительная информация

Итак, я отладил StandardServletMultipartResolver и обнаружил, что он использует ISO-8859-1-кодовую для его анализа. Это дает желаемые эффекты, даже если страницы кодируются в кодировке UTF-8, а объект запроса также имеет UTF-8-Charset. Я пытался заставить ISO-Charset с фильтром, как так

@Bean 
@Order(Ordered.HIGHEST_PRECEDENCE) 
public Filter characterEncodingFilter() { 
    CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); 
    characterEncodingFilter.setEncoding("ISO-8859-1"); 
    characterEncodingFilter.setForceEncoding(true); 
    return characterEncodingFilter; 
} 

, но по какой-то причине, CommonsMultipartResolver находит UTF-8 закодированные объект запроса, так как эта кодировка не работает, или у меня есть сделала еще одну ошибку, которую я не вижу.

Я также попытался найти точный момент выброшенного исключения, возможно, сам расширить класс и убедиться, что уже разрешенные данные формы сохранены, до сих пор безрезультатно.

Еще больше информации

Как предложено другой нитью здесь, я пытался заставить ISO-8859-1 по запросу. Сначала это полностью обходило CommonsMultipartResolver и испортило мой текст, теперь он фильтрует правильный резольвер, но в нем все еще говорится, что в многостраничных данных нет файлов. Просто для справки, Фильтровальный я использовал:

private class MyMultiPartFilter extends MultipartFilter { 

    @Override 
    protected void doFilterInternal(HttpServletRequest request, 
      HttpServletResponse response, FilterChain filterChain) 
      throws ServletException, IOException { 
     request.setCharacterEncoding("ISO-8859-1"); 
     request.getParameterNames(); 
     super.doFilterInternal(request, response, filterChain); 
    } 
} 

Сделано Bean из него и изменил название multipartResolver() - Фасоль в filterMultipartResolver()

ответ

0

Решение этой проблемы было найдено примерно в то время я искал его. Он был опубликован here.

В связи с тем, что WildFly и Undertow сталкиваются с трудностями при работе с StandardServletMultipartResolver, более эффективно (возможно, даже необходимо) использовать CommonsMultipartResolver. Однако это нужно вызвать до того, как будет обработана остальная часть POST-данных.
Чтобы обеспечить это, необходимо вызвать MultipartFilter и создать filterMultipartResolver -Bean так:

@Bean 
public CommonsMultipartResolver filterMultipartResolver() { 
    return new CommonsMultipartResolver(); 
} 

@Bean 
@Order(0) 
public MultipartFilter multipartFilter() { 
    return new MultipartFilter(); 
} 

Это гарантирует, что фильтр называется первым, а это в свою очередь вызывает распознаватель. Единственным недостатком является отсутствие готового способа ограничить отдельные файлы для загрузки. Это можно сделать, установив maxUploadSize(value), что ограничивает общий размер запроса.

Final Редактировать

Итак, вот что я в конечном итоге с помощью, которая позволяет эффективно загрузки и обработки больших файлов. Я не уверен, что это будет столь же эффективно при загрузке больших файлов, так как это обрабатывает негабаритные файлы после преобразования запроса в FileItems, но до разбора FileItems.

Я продлил CommonsMultipartResolver переопределить parseRequest как это:

@Override 
protected MultipartParsingResult parseRequest(HttpServletRequest request) { 

    String encoding = determineEncoding(request); 
    FileUpload fileUpload = prepareFileUpload(encoding); 

    List<FileItem> fileItems; 
    List<String> oversizedFields = new LinkedList<>(); 

    try { 
     fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); 
    } catch (FileUploadBase.SizeLimitExceededException ex) { 
     fileItems = Collections.emptyList(); 
     request.setAttribute(ATTR_REQUEST_SIZE_EXCEEDED, 
       KEY_REQUEST_SIZE_EXCEEDED); 
    } catch (FileUploadException ex) { 
     throw new MultipartException(MULTIPART_UPLOAD_ERROR, ex); 
    } 
    if (maxFileSize > -1) { 
     for (FileItem fileItem : fileItems) { 
      if (fileItem.getSize() > maxFileSize) { 
       oversizedFields.add(fileItem.getFieldName()); 
       fileItem.delete(); 
      } 
     } 
    } 
    if (!oversizedFields.isEmpty()) { 
     request.setAttribute(ATTR_FIELDS_OVERSIZED, oversizedFields); 
    } 
    return parseFileItems((List<FileItem>) fileItems, encoding); 
} 

и добавил методы для установки MaxFileSize с помощью конфигурации компонента. Если размер запроса будет превышен, все значения будут удалены, поэтому будьте осторожны с этим, особенно если вы используете _csrf-token или аналогичный.

В контроллере теперь легко проверить добавленный атрибут и разместить сообщения об ошибках на странице.

Смежные вопросы