Search code examples
springspring-bootspring-mvcthymeleaftomcat9

tomcat server doesn't mapped my real domain name


Spring boot application multipart get file doesn't working in production environment

I have a spring boot mvc web application with thymeleaf. So everything is working normally in local pc but in production environment (remote server) trying to view and download the file the url prefix is sticking localhost:8080/file-url/view not example.az/file-url/view. My appication server is tomcat 9.0.71 version in linux server.

So here is the code snippet

Controller class

@Controller
public class FileBBController {

    private final CategoryBBService categoryBBService;

    public FileBBController(CategoryBBService categoryBBService) {
        this.categoryBBService = categoryBBService;
    }

    @GetMapping("/files/bb/new")
    public String newFile() {
        return "legislation/bb/upload_form";
    }

    @PostMapping(value = "/files/bb/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file, Model model) {
        String message;
        try {
            categoryBBService.storeFile(file);
            message = "Fayl bazaya müvəfəqiyyətlə yükləndi: " + file.getOriginalFilename();
            model.addAttribute("message", message);
            Thread.sleep(4000);
        } catch (Exception e) {
            message = "Diqqət bir fayl seçməlisiniz!";
            model.addAttribute("message", message);
        }
        return "redirect:/bb/files";
    }

    @GetMapping("/bb/files")
    public String getFiles(Model model) {
        String keyword = null;
        return getOnePage(1, model, "createdAt", "desc", keyword);
    }

    @GetMapping("/files/{id}")
    public ResponseEntity<Resource> getFile(@PathVariable String id) {
        CategoryBB fileDB = categoryBBService.getFile(id);

        return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF)
                .header(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION,
                        "attachment; filename=\"" + fileDB.getName() + "\"")
                .body(new ByteArrayResource(fileDB.getData()));
    }

    @GetMapping("/bb/files/page/{pageNo}")
    public String getOnePage(@PathVariable("pageNo") int currentPage, Model model,
                             @RequestParam(value = "sortField", required = false) String sortField,
                             @RequestParam(value = "sortDir", required = false) String sortDir,
                             @RequestParam(value = "keyword", required = false) String keyword
    ) {
        Page<ResponseFile> page = categoryBBService.findPaginated(currentPage, PAGE_SIZE, sortField, sortDir, keyword).map(dbFile -> {
            String fileDownloadUri = ServletUriComponentsBuilder.
                    fromCurrentContextPath()
                    .path("/files/")
                    .path(dbFile.getId())
                    .toUriString();
            System.out.println(fileDownloadUri);
            return new ResponseFile(dbFile.getName(),
                    fileDownloadUri,
                    dbFile.getType(),
                    FileUtils.byteCountToDisplaySize(dbFile.getData().length),
                    dbFile.getCreatedAt());
        });
        List<ResponseFile> files = page.getContent();
        model.addAttribute("currentPage", currentPage);
        model.addAttribute("totalPages", page.getTotalPages());
        model.addAttribute("totalElements", page.getTotalElements());
        model.addAttribute("sortField", sortField);
        model.addAttribute("sortDir", sortDir);
        model.addAttribute("reverseSortDir", sortDir.equals("asc") ? "desc" : "asc");
        model.addAttribute("keyword", keyword);
        model.addAttribute("files", files);

        return "legislation/bb/files";
    }

    @RequestMapping(value = "/files/{id}/bb/delete", method = RequestMethod.GET)
    public String deleteFile(@PathVariable("id") String id) {
        categoryBBService.deleteFileById(id);
        return "redirect:/bb/files";
    }

    @GetMapping("/files/{id}/bb/view")
    public void viewFile(@PathVariable String id, HttpServletResponse resp) throws IOException {
        CategoryBB file = categoryBBService.getFile(id);
        byte[] byteArray = file.getData();
        resp.setContentLength(byteArray.length);
        try (OutputStream os = resp.getOutputStream()) {
            os.write(byteArray, 0, byteArray.length);
        }
    }

}

Service class

@Service
public class CategoryBBService {

    private final CategoryBBRepository categoryBBRepository;

    public CategoryBBService(CategoryBBRepository categoryBBRepository) {
        this.categoryBBRepository = categoryBBRepository;
    }

    public void storeFile(MultipartFile file) throws IOException {

        String fileName = StringUtils.getFilename(file.getOriginalFilename());
        if (StringUtils.endsWithIgnoreCase(fileName, ".pdf")) {
            CategoryBB fileDB = new CategoryBB(fileName, file.getContentType(), file.getBytes());
            categoryBBRepository.save(fileDB);
        } else {
            throw new RuntimeException("Wrong file type uploaded");
        }
    }

    public CategoryBB getFile(String id) {
        return categoryBBRepository.findById(id).orElse(null);
    }

    public Page<CategoryBB> findPaginated(int pageNo, int pageSize,
                                          String sortField,
                                          String sortDirection,
                                          String keyword) {
        Sort sort = SortUtil.sorting(sortDirection, sortField);
        Pageable pageable = PageRequest.of(pageNo - 1, pageSize, sort);
        if (keyword != null) {
            return categoryBBRepository.findAll(keyword, pageable);
        }
        return categoryBBRepository.findAll(pageable);
    }

    public void deleteFileById(String id) {
        categoryBBRepository.deleteById(id);
    }

}

Entity class

@Entity
@Table(name = "category_bb")
public class CategoryBB extends FileDB {

    public CategoryBB() {
    }

    public CategoryBB(String name, String type, byte[] data) {
        super(name, type, data);
    }

}

Super class for Entity

@MappedSuperclass
public abstract class FileDB implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;
    private String name;
    private String type;

    @Column(name = "file_url")
    private String fileUrl;
    @Lob
    private byte[] data;

    @CreationTimestamp
    @Column(name = "created_at")
    private LocalDateTime createdAt;

    public FileDB() {
    }

    public FileDB(String name, String type, byte[] data) {
        this.name = name;
        this.type = type;
        this.data = data;
    }

    public FileDB(String name, String type, String fileUrl, byte[] data) {
        this.name = name;
        this.type = type;
        this.fileUrl = fileUrl;
        this.data = data;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }

    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(LocalDateTime createdAt) {
        this.createdAt = createdAt;
    }

    public String getFileUrl() {
        return fileUrl;
    }

    public void setFileUrl(String fileUrl) {
        this.fileUrl = fileUrl;
    }

ResponseFile class

public class ResponseFile {
    private String name;
    private String url;
    private String type;
    private String size;

    public ResponseFile() {
    }

    public ResponseFile(String name, String url, String type, String size, LocalDateTime createdAt) {
        this.name = name;
        this.url = url;
        this.type = type;
        this.size = size;
        this.createdAt = createdAt;
    }

    private LocalDateTime createdAt;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }

    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(LocalDateTime createdAt) {
        this.createdAt = createdAt;
    }

}

files.html

<div th:if="${files.size() > 0}" class="table-responsive">
        <table class="table table-hover table-bordered border-primary">
            <thead class="thead-light table-secondary">
            <tr class="text-center">
                <th scope="col">№</th>
                <th scope="col">Faylın adı</th>
                <th scope="col">Faylı yüklə</th>
                <th scope="col">Fayla bax</th>
                <th scope="col">Ölçüsü</th>
                <th sec:authorize="hasRole('ADMIN')" scope="col">Actions</th>
                <th scope="col">
                    <a style="text-decoration: none; color: black"
                       th:href="@{'/bb/files/page/' + ${currentPage} + '?sortField=createdAt&sortDir=' + ${reverseSortDir} + ${keyword != null ? '&keyword=' + keyword : ''}}">
                        Tarix
                    </a>
                </th>
            </tr>
            </thead>
            <tbody class="table-group-divider">
            <tr th:each="file, fileStat : ${files}">
                <td class="text-center" th:text="${fileStat.count}"></td>
                <td th:text="${file.name}"></td>

                <td class="text-center"><a th:href="@{${file.url}}"><i class="fa-solid fa-cloud-arrow-down fa-lg"></i></a></td>
                <td class="text-center"><a target="_blank" th:href="@{${file.url} + '/bb/view/'}"><i class="fa fa-thin fa-eye fa-lg"></i></a></td>

                <td th:text="${file.size}"></td>
                <td th:text="${#temporals.format(file.createdAt, 'dd-MM-yyyy, HH:mm')}"></td>
                <td sec:authorize="hasRole('ADMIN')" class="text-center">
                    <a th:href="@{${file.url} + '/bb/delete/'}" th:fileName="${file.name}" style="border: none; background: none"
                      id="btnDelete" class="btn-delete" data-bs-toggle="modal" data-bs-target="#exampleModal">
                        <i class="fa-solid fa-trash fa-xl" style="color: red"></i>
                    </a>
                </td>
            </tr>
            </tbody>
        </table>
    </div>

When I click the buttton "Fayla bax (view file)" and "Faylı yüklə(download file)" it's prefix comming "http://localhost:8080/files/c9376e46-55cb-4cf5-beab-ba886bb23c5f/bb/view/" something like that. Would you help me solve this issue ?

enter image description here

enter image description here


Solution

  • based on comments:
    If you are using just spring-boot application without any reverse proxy (such as apache, nginx) try to configure spring's property server.forward-headers-strategy to either native or framework and it should resolve the issue.