Ввод и вывод двоичных потоков с использованием JERSEY?
Я использую Джерси для реализации RESTful API, который в первую очередь извлекает и обслуживает данные в кодировке JSON. Но у меня есть несколько ситуаций, когда мне нужно выполнить следующее:
- Экспортируйте загружаемые документы, такие как PDF, XLS, ZIP или другие двоичные файлы.
- Извлекайте многокомпонентные данные, такие как JSON плюс загруженный файл XLS
У меня есть одностраничный веб-клиент на основе JQuery, который создает вызовы AJAX для этого веб-сервиса. На данный момент он не отправляет формы, а использует GET и POST (с объектом JSON). Должен ли я использовать сообщение формы для отправки данных и прикрепленного двоичного файла, или я могу создать многочастный запрос с двоичным файлом JSON plus?
Сервисный слой моего приложения в настоящее время создает ByteArrayOutputStream, когда он генерирует файл PDF. Каков наилучший способ вывести этот поток клиенту через Джерси? Я создал MessageBodyWriter, но я не знаю, как использовать его из ресурса Джерси. Это правильный подход?
Я просматривал образцы, включенные в Джерси, но пока не нашел ничего, что иллюстрирует, как сделать любую из этих вещей. Если это имеет значение, я использую Джерси с Джексоном для выполнения Object->JSON без шага XML и на самом деле не использую JAX-RS.
10 ответов
Мне удалось получить ZIP-файл или PDF-файл, расширив StreamingOutput
объект. Вот пример кода:
@Path("PDF-file.pdf/")
@GET
@Produces({"application/pdf"})
public StreamingOutput getPDF() throws Exception {
return new StreamingOutput() {
public void write(OutputStream output) throws IOException, WebApplicationException {
try {
PDFGenerator generator = new PDFGenerator(getEntity());
generator.generatePDF(output);
} catch (Exception e) {
throw new WebApplicationException(e);
}
}
};
}
Класс PDFGenerator (мой собственный класс для создания PDF) берет выходной поток из метода write и записывает в него вместо вновь созданного выходного потока.
Не знаю, если это лучший способ сделать это, но это работает.
Я должен был вернуть файл RTF, и это сработало для меня.
// create a byte array of the file in correct format
byte[] docStream = createDoc(fragments);
return Response
.ok(docStream, MediaType.APPLICATION_OCTET_STREAM)
.header("content-disposition","attachment; filename = doc.rtf")
.build();
Я использую этот код для экспорта файла Excel (xlsx) ( Apache Poi) в Джерси в качестве приложения.
@GET
@Path("/{id}/contributions/excel")
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
public Response exportExcel(@PathParam("id") Long id) throws Exception {
Resource resource = new ClassPathResource("/xls/template.xlsx");
final InputStream inp = resource.getInputStream();
final Workbook wb = WorkbookFactory.create(inp);
Sheet sheet = wb.getSheetAt(0);
Row row = CellUtil.getRow(7, sheet);
Cell cell = CellUtil.getCell(row, 0);
cell.setCellValue("TITRE TEST");
[...]
StreamingOutput stream = new StreamingOutput() {
public void write(OutputStream output) throws IOException, WebApplicationException {
try {
wb.write(output);
} catch (Exception e) {
throw new WebApplicationException(e);
}
}
};
return Response.ok(stream).header("content-disposition","attachment; filename = export.xlsx").build();
}
Вот еще один пример. Я создаю QRCode как PNG через ByteArrayOutputStream
, Ресурс возвращает Response
объект, а данные потока - это сущность.
Чтобы проиллюстрировать обработку кода ответа, я добавил обработку заголовков кэша (If-modified-since
, If-none-matches
, так далее).
@Path("{externalId}.png")
@GET
@Produces({"image/png"})
public Response getAsImage(@PathParam("externalId") String externalId,
@Context Request request) throws WebApplicationException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
// do something with externalId, maybe retrieve an object from the
// db, then calculate data, size, expirationTimestamp, etc
try {
// create a QRCode as PNG from data
BitMatrix bitMatrix = new QRCodeWriter().encode(
data,
BarcodeFormat.QR_CODE,
size,
size
);
MatrixToImageWriter.writeToStream(bitMatrix, "png", stream);
} catch (Exception e) {
// ExceptionMapper will return HTTP 500
throw new WebApplicationException("Something went wrong …")
}
CacheControl cc = new CacheControl();
cc.setNoTransform(true);
cc.setMustRevalidate(false);
cc.setNoCache(false);
cc.setMaxAge(3600);
EntityTag etag = new EntityTag(HelperBean.md5(data));
Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(
updateTimestamp,
etag
);
if (responseBuilder != null) {
// Preconditions are not met, returning HTTP 304 'not-modified'
return responseBuilder
.cacheControl(cc)
.build();
}
Response response = Response
.ok()
.cacheControl(cc)
.tag(etag)
.lastModified(updateTimestamp)
.expires(expirationTimestamp)
.type("image/png")
.entity(stream.toByteArray())
.build();
return response;
}
Пожалуйста, не бейте меня в случае stream.toByteArray()
нет памяти нет смысла:) Это работает для моих файлов PNG <1KB...
Я создавал сервисы на Джерси 1.17 следующим образом:
FileStreamingOutput
public class FileStreamingOutput implements StreamingOutput {
private File file;
public FileStreamingOutput(File file) {
this.file = file;
}
@Override
public void write(OutputStream output)
throws IOException, WebApplicationException {
FileInputStream input = new FileInputStream(file);
try {
int bytes;
while ((bytes = input.read()) != -1) {
output.write(bytes);
}
} catch (Exception e) {
throw new WebApplicationException(e);
} finally {
if (output != null) output.close();
if (input != null) input.close();
}
}
}
GET
@GET
@Produces("application/pdf")
public StreamingOutput getPdf(@QueryParam(value="name") String pdfFileName) {
if (pdfFileName == null)
throw new WebApplicationException(Response.Status.BAD_REQUEST);
if (!pdfFileName.endsWith(".pdf")) pdfFileName = pdfFileName + ".pdf";
File pdf = new File(Settings.basePath, pdfFileName);
if (!pdf.exists())
throw new WebApplicationException(Response.Status.NOT_FOUND);
return new FileStreamingOutput(pdf);
}
И клиент, если вам это нужно:
Client
private WebResource resource;
public InputStream getPDFStream(String filename) throws IOException {
ClientResponse response = resource.path("pdf").queryParam("name", filename)
.type("application/pdf").get(ClientResponse.class);
return response.getEntityInputStream();
}
В этом примере показано, как публиковать файлы журналов в JBoss через ресурс отдыха. Обратите внимание, что метод get использует интерфейс StreamingOutput для потоковой передачи содержимого файла журнала.
@Path("/logs/")
@RequestScoped
public class LogResource {
private static final Logger logger = Logger.getLogger(LogResource.class.getName());
@Context
private UriInfo uriInfo;
private static final String LOG_PATH = "jboss.server.log.dir";
public void pipe(InputStream is, OutputStream os) throws IOException {
int n;
byte[] buffer = new byte[1024];
while ((n = is.read(buffer)) > -1) {
os.write(buffer, 0, n); // Don't allow any extra bytes to creep in, final write
}
os.close();
}
@GET
@Path("{logFile}")
@Produces("text/plain")
public Response getLogFile(@PathParam("logFile") String logFile) throws URISyntaxException {
String logDirPath = System.getProperty(LOG_PATH);
try {
File f = new File(logDirPath + "/" + logFile);
final FileInputStream fStream = new FileInputStream(f);
StreamingOutput stream = new StreamingOutput() {
@Override
public void write(OutputStream output) throws IOException, WebApplicationException {
try {
pipe(fStream, output);
} catch (Exception e) {
throw new WebApplicationException(e);
}
}
};
return Response.ok(stream).build();
} catch (Exception e) {
return Response.status(Response.Status.CONFLICT).build();
}
}
@POST
@Path("{logFile}")
public Response flushLogFile(@PathParam("logFile") String logFile) throws URISyntaxException {
String logDirPath = System.getProperty(LOG_PATH);
try {
File file = new File(logDirPath + "/" + logFile);
PrintWriter writer = new PrintWriter(file);
writer.print("");
writer.close();
return Response.ok().build();
} catch (Exception e) {
return Response.status(Response.Status.CONFLICT).build();
}
}
}
Использование Jersey 2.16 Загрузка файлов очень проста.
Ниже приведен пример для файла ZIP
@GET
@Path("zipFile")
@Produces("application/zip")
public Response getFile() {
File f = new File(ZIP_FILE_PATH);
if (!f.exists()) {
throw new WebApplicationException(404);
}
return Response.ok(f)
.header("Content-Disposition",
"attachment; filename=server.zip").build();
}
Я нашел следующее полезное для меня, и я хотел бы поделиться, если это поможет вам или кому-то еще. Я хотел что-то вроде MediaType.PDF_TYPE, которого не существует, но этот код делает то же самое:
DefaultMediaTypePredictor.CommonMediaTypes.
getMediaTypeFromFileName("anything.pdf")
В моем случае я размещал документ PDF на другом сайте:
FormDataMultiPart p = new FormDataMultiPart();
p.bodyPart(new FormDataBodyPart(FormDataContentDisposition
.name("fieldKey").fileName("document.pdf").build(),
new File("path/to/document.pdf"),
DefaultMediaTypePredictor.CommonMediaTypes
.getMediaTypeFromFileName("document.pdf")));
Затем p передается как второй параметр post().
Эта ссылка помогла мне собрать этот фрагмент кода: http://jersey.576304.n2.nabble.com/Multipart-Post-td4252846.html
Со мной это работало нормально: http://example.com/rest/muqsith/get-file?filePath=C: \ Users \ I066807 \ Desktop \ test.xml
@GET
@Produces({ MediaType.APPLICATION_OCTET_STREAM })
@Path("/get-file")
public Response getFile(@Context HttpServletRequest request){
String filePath = request.getParameter("filePath");
if(filePath != null && !"".equals(filePath)){
File file = new File(filePath);
StreamingOutput stream = null;
try {
final InputStream in = new FileInputStream(file);
stream = new StreamingOutput() {
public void write(OutputStream out) throws IOException, WebApplicationException {
try {
int read = 0;
byte[] bytes = new byte[1024];
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
} catch (Exception e) {
throw new WebApplicationException(e);
}
}
};
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return Response.ok(stream).header("content-disposition","attachment; filename = "+file.getName()).build();
}
return Response.ok("file path null").build();
}
Другой пример кода, в который вы можете загрузить файл в службу REST, служба REST заархивирует файл, и клиент загрузит файл zip с сервера. Это хороший пример использования двоичных потоков ввода и вывода с использованием Джерси.
/questions/1775497/zaschischennyij-parolem-pochtovyij-fajl-v-java/1775508#1775508
Этот ответ был опубликован мной в другой ветке. Надеюсь это поможет.