Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[Java] servlet caching problemen

Pagina: 1
Acties:

  • FireFly3k
  • Registratie: Augustus 2002
  • Laatst online: 21:20
Ik ben bezig met een servlet die vanuit een bepaalde directory plaatjes laat zien. Deze plaatjes kan ik via een form uploaden en verwijderen. Het uploaden en verwijderen gaat goed. Als ik een plaatje opvraag wordt deze goed afgebeeld. het probleem begint wanneer ik een plaatje verwijder met bijvoorbeeld de naam "plaatje_1". Als ik dan weer een nieuw plaatje met de naam "plaatje_1" upload en deze dan opvraag krijg ik weer de oude te zien.

Wat heb ik allemaal gecontroleerd.
Als ik een plaatje verwijder dan is deze ook echt weg.
Als ik een nieuw plaatje upload dan staat ook echt het nieuwe plaatje op de juiste plaats met de juiste naam.
Ik stuur met de plaatjes headers mee zodat de servlet niet gecached wordt maar ik denk dat dit alleen geldt voor proxies enzo.
Als ik Tomcat opnieuw opstart dan wordt ineens wel het nieuwe plaatje geladen.
Het is wel mogelijk om "plaatje_1" en "plaatje_2" te laten zien maar zodra het dezelfde naam heeft gaat het mis.

De software die ik gebruik is, Java 1.5 met Tomcat 5.5 en heb het getest op Windows Vista en XP.

Het lijkt erop dat Tomcat de servlets cached maar ik kan er niet achter komen hoe ik dit tegen ga.
Kent iemand dit probleem of weet iemand misschien hoe ik dit zou kunnen oplossen?

PSN - 500px - Flickr


  • den 150
  • Registratie: Oktober 2002
  • Niet online
Tomcat cachet absoluut niets, anders zou er niets dynamisch zijn aan een servlet natuurlijk. Vermoedelijk cachet je browser. Welke headers stuur je mee? Probeer eens je browser cache te clearen. Probeer eens CTRL-F5 ipv F5.

  • Janoz
  • Registratie: Oktober 2000
  • Laatst online: 30-11 11:35

Janoz

Moderator Devschuur®

!litemod

Hoe stuur je de headers met het plaatje mee? Laat eens een stukje code zien van je passthrough servlet? (En dan bedoel ik vooral de doGet() methode)

Ik vermoed namelijk dat je helemaal geen headers met het plaatje meestuurt, maar met de html waar het plaatje in staat.

Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'


  • reddevil
  • Registratie: Februari 2001
  • Laatst online: 06-10 14:25
den 150 schreef op woensdag 26 september 2007 @ 00:23:
Tomcat cachet absoluut niets, anders zou er niets dynamisch zijn aan een servlet natuurlijk. Vermoedelijk cachet je browser. Welke headers stuur je mee? Probeer eens je browser cache te clearen. Probeer eens CTRL-F5 ipv F5.
Ik weet wel zeker dat de browser het plaatje cached, dit probleem hebben wij ook regelmatig bij ontwikkeling.

  • BalusC
  • Registratie: Oktober 2000
  • Niet online

BalusC

Carpe diem

Bekend issue met IE. Met FireFox moet het wel goed werken overigens.

Ranzige workaround:
code:
1
<img src="foo.gif?<%= new java.util.Date().getTime() %>" />
Dit laat IE doen denken dat het om een nieuw plaatje gaat, waardoor het tot opnieuw opvragen forceert.

Of schrijf een ImageServlet en gebruik die in plaats daarvan. Een goed voorbeeld staat hier: http://balusc.blogspot.com/2007/04/imageservlet.html

[ Voor 14% gewijzigd door BalusC op 26-09-2007 12:45 ]


  • rrrandy
  • Registratie: Juli 2005
  • Laatst online: 27-06 13:00
Een mooiere oplossing vind ik om een servlet filter te implementeren dat images (wellicht nog andere bestanden) de juiste headers geeft zodat de client weet dat ze niet gecached mogen worden.

/edit: zoiets dus
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
{
    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
    
    if  (httpServletRequest.getProtocol().equals("HTTP/1.0"))
    {
        httpServletResponse.setHeader("Pragma", "no-cache");
        httpServletResponse.setDateHeader("Expires", -1);
    }
    else if (httpServletRequest.getProtocol().equals("HTTP/1.1"))
    {
        httpServletResponse.setHeader("Cache-Control", "max-age=1");
    }       
    chain.doFilter(httpServletRequest, httpServletResponse);
}

[ Voor 67% gewijzigd door rrrandy op 26-09-2007 13:29 ]


  • FireFly3k
  • Registratie: Augustus 2002
  • Laatst online: 21:20
Bedankt voor jullie reacties. Op dit moment heb ik even de code niet bij de hand helaas aangezien ik parttime werk :/.

Zowel F5 als ctrl + F5 halen niets uit, hetzelfde plaatje wordt nog steeds getoond. Zelfs als ik in Firefox een nieuw plaatje upload en deze vervolgens in IE of zelfs vanaf een andere pc opvraag blijft het oude plaatje getoond worden. Vandaar dus ook de gedachte dat Tomcat het cached...

Ik ga zeker even de "ranzige" workaround van BalusC proberen en die ImageServlet bekijken. Misschien dat mijn ImageServlet wat fouten heeft (wat waarschijnlijk zo is :p)...

De headers die ik op dit moment mee stuur zijn:
code:
1
2
3
4
5
6
7
8
9
10
11
  // Set to expire far in the past.
  res.setHeader("Expires", "Sat, 6 May 1995 12:00:00 GMT");

  // Set standard HTTP/1.1 no-cache headers.
  res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");

  // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
  res.addHeader("Cache-Control", "post-check=0, pre-check=0");

  // Set standard HTTP/1.0 no-cache header.
  res.setHeader("Pragma", "no-cache");

Die heb ik uit het volgende artikel gevonden, http://www.onjava.com/pub...xcerpt/jebp_3/index2.html

Ik ga proberen om mijn code op te vragen en dan zal ik die posten. Ondertussen ga ik vast de ImageServlet van BalusC proberen.

PSN - 500px - Flickr


Verwijderd

BalusC schreef op woensdag 26 september 2007 @ 12:44:
Ranzige workaround:
code:
1
<img src="foo.gif?<%= new java.util.Date().getTime() %>" />
Het is inderdaad niet zo schoon vanwege twee redenen. Eentje is het volgende:
code:
1
<img src="foo.gif?<%= System.currentTimeMillis() %>" />

;)

  • BalusC
  • Registratie: Oktober 2000
  • Niet online

BalusC

Carpe diem

Die "Expires" header zou het beste op -1 gezet moeten worden. Maar deze wordt alsnog overriden door de "no-cache" bepaling van de cache-control.

De beste aanpak qua effectiviteit en compatibility is:
Java:
1
2
response.setHeader("cache-control", "private, no-cache, no-store, no-transform, must-revalidate"); // HTTP 1.1
response.setHeader("pragma", "no-cache"); // HTTP 1.0

  • bloody
  • Registratie: Juni 1999
  • Laatst online: 21:16

bloody

0.000 KB!!

Kijk met een sniffer of je de content-length ziet veranderen.
Als dit wel zo is, ligt het bljkbaar aan je browser..
Anders: server of een proxy daar tussen in.

btw: die hierboven vaak aangehaalde pragma heeft niet zo veel nut: wordt heel slecht ondersteund. Zie voor achtergrond informatie: http://www.mnot.net/cache_docs/

Om een lang verhaal kort te maken: gebruik de Cache-Control (met de juiste opties) en Expires header.

nope


  • FireFly3k
  • Registratie: Augustus 2002
  • Laatst online: 21:20
Ik heb de code binnen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        String fotopath = request.getParameter("fotopath");
        String filename = null;
        
        try {
            for (int i = 0; i < IMAGE_TYPES.length; i++) {
                filename = savepath + fotopath + "." + IMAGE_TYPES[i];
                if (FileHandler.isFile(filename)) {
                    break;
                } else {
                    filename = null;
                }
            }
            
            String split[] = filename.split("[.]");
            String ext = split[split.length - 1];
            
            
//          Set to expire far in the past.
            response.setHeader("Expires", "Sat, 6 May 1995 12:00:00 GMT");
//           Set standard HTTP/1.1 no-cache headers.
            response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
//           Set IE extended HTTP/1.1 no-cache headers (use addHeader).
            response.addHeader("Cache-Control", "post-check=0, pre-check=0");
//           Set standard HTTP/1.0 no-cache header.
            response.setHeader("Pragma", "no-cache");
            response.setContentType("image/" + ext);

            
            if (filename != null) {
                ImageIcon image = new ImageIcon(filename);
                
                BufferedImage bufferedImage = new BufferedImage(image
                        .getIconWidth(), image.getIconHeight(),
                        BufferedImage.TYPE_INT_RGB);

                // Draw image
                Graphics2D g2d = (Graphics2D) bufferedImage.getGraphics();
                g2d.drawImage(image.getImage(), 0, 0, null);

                // Free graphic resources
                g2d.dispose();
                
                OutputStream out = response.getOutputStream();
                ImageIO.write(bufferedImage, ext, out);
                out.close();
                
                image = null;
                g2d = null;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


Het verschil met de servlet van BalusC is de manier waarop het plaatje ingeladen wordt. Ik teken hem echt en BalusC leest het bestand in en gooit die direct naar de Servlet. Klopt het wat ik zeg?
Ik ga het zometeen even testen in een klein projectje :9.

PSN - 500px - Flickr


  • voodooless
  • Registratie: Januari 2002
  • Laatst online: 30-11 11:20

voodooless

Sound is no voodoo!

Het is voor iedere web developer van groot belang om goed duidelijk te krijgen welke cache optie wat doet. Al die HTTP headers zijn echt horror, en als je een servlet omgeving hebt die zelf ook nog eens kan cachen, wordt het nog eens een keer extra tricky, zeker als gaat werken met gebruikers afgeschermde gedeelten. Ook een caching proxy server kan daarbij problemen geven als je niet de juiste headers zet. Dan krijgen users opeens gegevens van anderen te zien enzo... geen feest dus :X Als je ook https gebruikt, check ook daar even of het werkt, want wij hebben al vaker gezien dat hier het caching gedrag anders uitpakte dan bij normaal http (op zelfde server met zelfde webapp).

Over je code: waarom eerst het image in memory laden, en het dan weer wegschrijven? Dat is f**king traag en zeer onnodig (in de meeste situaties)!

[ Voor 9% gewijzigd door voodooless op 26-09-2007 21:10 ]

Do diamonds shine on the dark side of the moon :?


  • bloody
  • Registratie: Juni 1999
  • Laatst online: 21:16

bloody

0.000 KB!!

voodooless schreef op woensdag 26 september 2007 @ 21:04:
Het is voor iedere web developer van groot belang om goed duidelijk te krijgen welke cache optie wat doet.
Vandaar mijn link ook :P
Over je code: waarom eerst het image in memory laden, en het dan weer wegschrijven? Dat is f**king traag en zeer onnodig (in de meeste situaties)!
idd, gewoon de hele handel streamen. Dus in brokken lezen van de file en schrijven naar de ServletOutputStream.

nope


  • FireFly3k
  • Registratie: Augustus 2002
  • Laatst online: 21:20
voodooless schreef op woensdag 26 september 2007 @ 21:04:
Het is voor iedere web developer van groot belang om goed duidelijk te krijgen welke cache optie wat doet. Al die HTTP headers zijn echt horror, en als je een servlet omgeving hebt die zelf ook nog eens kan cachen, wordt het nog eens een keer extra tricky, zeker als gaat werken met gebruikers afgeschermde gedeelten. Ook een caching proxy server kan daarbij problemen geven als je niet de juiste headers zet. Dan krijgen users opeens gegevens van anderen te zien enzo... geen feest dus :X Als je ook https gebruikt, check ook daar even of het werkt, want wij hebben al vaker gezien dat hier het caching gedrag anders uitpakte dan bij normaal http (op zelfde server met zelfde webapp).

Over je code: waarom eerst het image in memory laden, en het dan weer wegschrijven? Dat is f**king traag en zeer onnodig (in de meeste situaties)!
Ja van al die caching opties wordt je wel gek 8)7. Ik had hem nog gewoon lokaal draaien maar dat was ik vergeten te melden.
Ik ga mijn code zeker nog even doornemen en thnx voor de comments :).

Heb even een klein test projectje gemaakt met de code van BalusC en die werkt perfect!!! _/-\o_
Mijn dank is groot, bedankt voor alle reacties!!! _/-\o_

PSN - 500px - Flickr


  • BalusC
  • Registratie: Oktober 2000
  • Niet online

BalusC

Carpe diem

Goed werk :)

Er schort wel meer aan je oorspronkelijke code .. Stel dat die eerste 'else' blok wordt uitgevoerd, dan krijg je geheid een NPE. Het sniffen van de extensie is ook nogal loos. Wat als er zowel "image.jpg" als "image.gif" in dezelfde map bestaan? Voorts zal ik het maar niet over het gebruik van BufferedImage en Graphics2D hebben :X Maargoed die code gebruik je als het goed is toch niet meer ;)
bloody schreef op woensdag 26 september 2007 @ 21:22:
idd, gewoon de hele handel streamen. Dus in brokken lezen van de file en schrijven naar de ServletOutputStream.
Gewoon BufferedInputStream/BufferedOutputStream gebruiken, dan hoef je het bestand niet handmatig in brokken te lezen.

[ Voor 39% gewijzigd door BalusC op 26-09-2007 21:38 ]


  • FireFly3k
  • Registratie: Augustus 2002
  • Laatst online: 21:20
Ik had mijn voorbeeld van watermark Servlet. Daarbij was het dus nodig om op de plaatje te tekenen.
Ik ga je code zeker nog even doornemen. :)

PSN - 500px - Flickr


  • voodooless
  • Registratie: Januari 2002
  • Laatst online: 30-11 11:20

voodooless

Sound is no voodoo!

BalusC schreef op woensdag 26 september 2007 @ 21:35:
Gewoon BufferedInputStream/BufferedOutputStream gebruiken, dan hoef je het bestand niet handmatig in brokken te lezen.
Um, je moet de streams nog altijd aan elkaar verbinden, en tenzij je geen pipe gebruikt, zul je nog steeds het heen en weer gooien van data zelf moeten doen met een loopje. Daar ga je dan echt niet zitten kutten om dat byte voor byte te doen natuurlijk. Dan kun je net zo goed je buffered streams weggooien ;) Gebruik dan nog steeds gewoon lekker een byte[] buffer om data heen en weer te gooien. Dat is nog steeds de snelste manier.

Als je plaatjes gaat tekenen, zou je kunnen overwegen om dat zelf te cachen. Dit soort ongein is een behoorlijke belasting voor de server als je het vaak doet. Het handigste is dan om een temp dir aan te maken waar je de gecreëerde plaatjes in zet, met als filenaam een hash van filedatum en filenaam ofzo. Zo kan je snel je file terugvinden als je die al gemaakt hebt en teruggeven, of anders aanmaken en opslaan. Let er wel op dat je die temp dir niet eeuwig laat groeien. Haal gewoon als er teveel in staat de oudste zooi weg ofzo. (je zou bij iedere read de file even kunnen touchen zodat de datum aangepast wordt).

[ Voor 31% gewijzigd door voodooless op 27-09-2007 08:09 ]

Do diamonds shine on the dark side of the moon :?


  • BalusC
  • Registratie: Oktober 2000
  • Niet online

BalusC

Carpe diem

Ik zie niet in waarom dit een 'gekut' is? Heb je de snelheid ook zelf gebenchmarkt?

  • voodooless
  • Registratie: Januari 2002
  • Laatst online: 30-11 11:20

voodooless

Sound is no voodoo!

BalusC schreef op donderdag 27 september 2007 @ 08:11:
Ik zie niet in waarom dit een 'gekut' is? Heb je de snelheid ook zelf gebenchmarkt?
Ja, heb ik meerdere malen gedaan, en 't maakt echt wel uit. Natuurlijk is het een en ander ook afhankelijk van de hoeveelheid data die je wil verplaatsen. Bij kleine hoeveelheden is de winst niet zo groot omdat het toch al best snel gaat. Besides, 't is net zo makkelijk te implementeren:

Java:
1
2
3
4
5
6
7
BufferenInputStream bin = ... ;
BufferenOutputStream bout = ...;

byte[] buffer = new byte[BUFFERSIZE];
while((int rd = bin.read(buffer))!=-1){
  bout.write(buffer,0,rd);
}


t.o.v.:
Java:
1
2
3
4
5
BufferenInputStream bin = ... ;
BufferenOutputStream bout = ...;
while((int rd = bin.read())!=-1){
  bout.write(rd);
}


Geef mijn dan maar die extra regel code :)

Do diamonds shine on the dark side of the moon :?


  • BalusC
  • Registratie: Oktober 2000
  • Niet online

BalusC

Carpe diem

Sja, het zal per JVM verschillen. De in mijn blog genoemde versie was iig het snelst @ WAS 5.x met IBM JDK 1.4 en Glassfish 2.x met JEE5, ongeacht de bestandsgrootte. Toepassen van een bufferblok op BufferedInputStream/BufferedOutputStream is overigens zinloos. Het wordt al daarin gedaan.

[ Voor 44% gewijzigd door BalusC op 27-09-2007 10:45 ]


  • voodooless
  • Registratie: Januari 2002
  • Laatst online: 30-11 11:20

voodooless

Sound is no voodoo!

BalusC schreef op donderdag 27 september 2007 @ 10:40:Toepassen van een bufferblok op BufferedInputStream/BufferedOutputStream is overigens zinloos. Het wordt al daarin gedaan.
Leuk dat er veel data in een keer wordt ingelezen uit je file/stream, maar zodra je het daarna een voor een gaan verwerken, creëer je nog steeds een bottleneck.

't is alsof je twee grote emmers hebt, eentje vol met water. Vervolgens ga je met een klein bekertje het water van de ene naar de andere emmer scheppen. Jij snapt ook wel dat je met een grotere beker veel sneller klaar bent. Laat die grote emmer dan weer vullen uit een revier ofzo, en het model is compleet :+

[ Voor 28% gewijzigd door voodooless op 27-09-2007 11:04 ]

Do diamonds shine on the dark side of the moon :?


  • BalusC
  • Registratie: Oktober 2000
  • Niet online

BalusC

Carpe diem

Je mist een factor: de snelheid waarmee het beker(tje) wordt gevuld.

  • voodooless
  • Registratie: Januari 2002
  • Laatst online: 30-11 11:20

voodooless

Sound is no voodoo!

BalusC schreef op donderdag 27 september 2007 @ 13:02:
Je mist een factor: de snelheid waarmee het beker(tje) wordt gevuld.
Ik kan je nu al zeggen dat je die grote beker sneller kunt vullen dan vele kleintjes, alleen al omdat je dat met een simpele memcopy kan doen die (deels) native geïmplementeerd is.

Anyway.. 't gaat wel allemaal erg offtopic zo.

[ Voor 6% gewijzigd door voodooless op 27-09-2007 13:20 ]

Do diamonds shine on the dark side of the moon :?

Pagina: 1