Skip to content

Commit e668e77

Browse files
committed
Wrap PrintWriter in StandardServletAsyncWebRequest
Closes gh-32342
1 parent 30c75ff commit e668e77

File tree

1 file changed

+271
-4
lines changed

1 file changed

+271
-4
lines changed

spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java

Lines changed: 271 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
package org.springframework.web.context.request.async;
1818

1919
import java.io.IOException;
20+
import java.io.PrintWriter;
2021
import java.util.ArrayList;
2122
import java.util.List;
23+
import java.util.Locale;
24+
import java.util.concurrent.locks.Lock;
2225
import java.util.concurrent.locks.ReentrantLock;
2326
import java.util.function.Consumer;
2427

@@ -226,6 +229,9 @@ private static final class LifecycleHttpServletResponse extends HttpServletRespo
226229
@Nullable
227230
private ServletOutputStream outputStream;
228231

232+
@Nullable
233+
private PrintWriter writer;
234+
229235
public LifecycleHttpServletResponse(HttpServletResponse response) {
230236
super(response);
231237
}
@@ -243,6 +249,15 @@ public ServletOutputStream getOutputStream() {
243249
}
244250
return this.outputStream;
245251
}
252+
253+
@Override
254+
public PrintWriter getWriter() throws IOException {
255+
if (this.writer == null) {
256+
Assert.notNull(this.asyncWebRequest, "Not initialized");
257+
this.writer = new LifecyclePrintWriter(getResponse().getWriter(), this.asyncWebRequest);
258+
}
259+
return this.writer;
260+
}
246261
}
247262

248263

@@ -340,6 +355,11 @@ private void obtainLockAndCheckState() throws AsyncRequestNotUsableException {
340355
}
341356
}
342357

358+
private void handleIOException(IOException ex, String msg) throws AsyncRequestNotUsableException {
359+
this.asyncWebRequest.transitionToErrorState();
360+
throw new AsyncRequestNotUsableException(msg, ex);
361+
}
362+
343363
private void releaseLock() {
344364
if (state() != State.NEW) {
345365
stateLock().unlock();
@@ -350,15 +370,262 @@ private State state() {
350370
return this.asyncWebRequest.state;
351371
}
352372

353-
private ReentrantLock stateLock() {
373+
private Lock stateLock() {
354374
return this.asyncWebRequest.stateLock;
355375
}
356376

357-
private void handleIOException(IOException ex, String msg) throws AsyncRequestNotUsableException {
358-
this.asyncWebRequest.transitionToErrorState();
359-
throw new AsyncRequestNotUsableException(msg, ex);
377+
}
378+
379+
380+
/**
381+
* Wraps a PrintWriter to prevent use after Servlet container onError
382+
* notifications, and after async request completion.
383+
*/
384+
private static final class LifecyclePrintWriter extends PrintWriter {
385+
386+
private final PrintWriter delegate;
387+
388+
private final StandardServletAsyncWebRequest asyncWebRequest;
389+
390+
private LifecyclePrintWriter(PrintWriter delegate, StandardServletAsyncWebRequest asyncWebRequest) {
391+
super(delegate);
392+
this.delegate = delegate;
393+
this.asyncWebRequest = asyncWebRequest;
394+
}
395+
396+
@Override
397+
public void flush() {
398+
if (tryObtainLockAndCheckState()) {
399+
try {
400+
this.delegate.flush();
401+
}
402+
finally {
403+
releaseLock();
404+
}
405+
}
406+
}
407+
408+
@Override
409+
public void close() {
410+
if (tryObtainLockAndCheckState()) {
411+
try {
412+
this.delegate.close();
413+
}
414+
finally {
415+
releaseLock();
416+
}
417+
}
418+
}
419+
420+
@Override
421+
public boolean checkError() {
422+
return this.delegate.checkError();
423+
}
424+
425+
@Override
426+
public void write(int c) {
427+
if (tryObtainLockAndCheckState()) {
428+
try {
429+
this.delegate.write(c);
430+
}
431+
finally {
432+
releaseLock();
433+
}
434+
}
435+
}
436+
437+
@Override
438+
public void write(char[] buf, int off, int len) {
439+
if (tryObtainLockAndCheckState()) {
440+
try {
441+
this.delegate.write(buf, off, len);
442+
}
443+
finally {
444+
releaseLock();
445+
}
446+
}
447+
}
448+
449+
@Override
450+
public void write(char[] buf) {
451+
this.delegate.write(buf);
452+
}
453+
454+
@Override
455+
public void write(String s, int off, int len) {
456+
if (tryObtainLockAndCheckState()) {
457+
try {
458+
this.delegate.write(s, off, len);
459+
}
460+
finally {
461+
releaseLock();
462+
}
463+
}
464+
}
465+
466+
@Override
467+
public void write(String s) {
468+
this.delegate.write(s);
469+
}
470+
471+
private boolean tryObtainLockAndCheckState() {
472+
if (state() == State.NEW) {
473+
return true;
474+
}
475+
if (stateLock().tryLock()) {
476+
if (state() == State.ASYNC) {
477+
return true;
478+
}
479+
stateLock().unlock();
480+
}
481+
return false;
482+
}
483+
484+
private void releaseLock() {
485+
if (state() != State.NEW) {
486+
stateLock().unlock();
487+
}
488+
}
489+
490+
private State state() {
491+
return this.asyncWebRequest.state;
492+
}
493+
494+
private Lock stateLock() {
495+
return this.asyncWebRequest.stateLock;
496+
}
497+
498+
// Plain delegates
499+
500+
@Override
501+
public void print(boolean b) {
502+
this.delegate.print(b);
503+
}
504+
505+
@Override
506+
public void print(char c) {
507+
this.delegate.print(c);
508+
}
509+
510+
@Override
511+
public void print(int i) {
512+
this.delegate.print(i);
513+
}
514+
515+
@Override
516+
public void print(long l) {
517+
this.delegate.print(l);
518+
}
519+
520+
@Override
521+
public void print(float f) {
522+
this.delegate.print(f);
523+
}
524+
525+
@Override
526+
public void print(double d) {
527+
this.delegate.print(d);
528+
}
529+
530+
@Override
531+
public void print(char[] s) {
532+
this.delegate.print(s);
533+
}
534+
535+
@Override
536+
public void print(String s) {
537+
this.delegate.print(s);
538+
}
539+
540+
@Override
541+
public void print(Object obj) {
542+
this.delegate.print(obj);
543+
}
544+
545+
@Override
546+
public void println() {
547+
this.delegate.println();
548+
}
549+
550+
@Override
551+
public void println(boolean x) {
552+
this.delegate.println(x);
553+
}
554+
555+
@Override
556+
public void println(char x) {
557+
this.delegate.println(x);
360558
}
361559

560+
@Override
561+
public void println(int x) {
562+
this.delegate.println(x);
563+
}
564+
565+
@Override
566+
public void println(long x) {
567+
this.delegate.println(x);
568+
}
569+
570+
@Override
571+
public void println(float x) {
572+
this.delegate.println(x);
573+
}
574+
575+
@Override
576+
public void println(double x) {
577+
this.delegate.println(x);
578+
}
579+
580+
@Override
581+
public void println(char[] x) {
582+
this.delegate.println(x);
583+
}
584+
585+
@Override
586+
public void println(String x) {
587+
this.delegate.println(x);
588+
}
589+
590+
@Override
591+
public void println(Object x) {
592+
this.delegate.println(x);
593+
}
594+
595+
@Override
596+
public PrintWriter printf(String format, Object... args) {
597+
return this.delegate.printf(format, args);
598+
}
599+
600+
@Override
601+
public PrintWriter printf(Locale l, String format, Object... args) {
602+
return this.delegate.printf(l, format, args);
603+
}
604+
605+
@Override
606+
public PrintWriter format(String format, Object... args) {
607+
return this.delegate.format(format, args);
608+
}
609+
610+
@Override
611+
public PrintWriter format(Locale l, String format, Object... args) {
612+
return this.delegate.format(l, format, args);
613+
}
614+
615+
@Override
616+
public PrintWriter append(CharSequence csq) {
617+
return this.delegate.append(csq);
618+
}
619+
620+
@Override
621+
public PrintWriter append(CharSequence csq, int start, int end) {
622+
return this.delegate.append(csq, start, end);
623+
}
624+
625+
@Override
626+
public PrintWriter append(char c) {
627+
return this.delegate.append(c);
628+
}
362629
}
363630

364631

0 commit comments

Comments
 (0)