<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2024-11-09T20:16:57+00:00</updated><id>/feed.xml</id><title type="html">Vahe Karamyan | Software Engineer</title><subtitle>Personal developer-blog, where frustrating and not frustrating tasks are updated here</subtitle><entry><title type="html">GIS: Terrain Elevated Tinted Hillshade</title><link href="/gis/2024/11/09/terrain-elevated-tinted-hillshade.html" rel="alternate" type="text/html" title="GIS: Terrain Elevated Tinted Hillshade" /><published>2024-11-09T00:00:00+00:00</published><updated>2024-11-09T00:00:00+00:00</updated><id>/gis/2024/11/09/terrain-elevated-tinted-hillshade</id><content type="html" xml:base="/gis/2024/11/09/terrain-elevated-tinted-hillshade.html"><![CDATA[<p><img src="/assets/photos/final-terrain-preview.jpeg" alt="Image" /></p>

<p>Ever wanted to create hillshaded terrain like the one in this picture? This guide is for you! The area shown here centers over Armenia—a country that should be on your list of places to explore. Look at those mountains! ^_^</p>

<h1 id="required-data">Required Data</h1>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
  <li>DEM (DSM or DTM) raw files for the region</li>
  <li>GDAL</li>
  <li>QGIS</li>
  <li>GeoServer instance with:
    <ul>
      <li>YSLD plugin</li>
      <li>Image Mosaic plugin</li>
    </ul>
  </li>
</ul>

<h2 id="creating-contoured-hillshade-files">Creating Contoured Hillshade Files</h2>
<p>To produce the tinted hillshade, you need elevation data for the area, provided through DEM (Digital Elevation Model) files, which are graphical representations of elevation. DEM files, often in GeoTIFF format, typically contain one band whose values indicate the elevation at each point. Various sources offer elevation data, such as Copernicus DEM, NASA Earthdata, etc.</p>

<p>Once you have the DEM data and open it in QGIS, it will appear as a dull gray image like this:</p>

<p><img src="/assets/photos/dem-raw-preview.jpeg" alt="Image" /></p>

<p>Next, we’ll create contoured hillshades from the raw DEM files.</p>

<h3 id="script-for-generating-hillshaded-geotiffs">Script for Generating Hillshaded GeoTIFFs</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="c"># Folder containing input files</span>
<span class="nv">input_folder</span><span class="o">=</span><span class="s2">"./raw"</span>
<span class="nv">hillshade_output_folder</span><span class="o">=</span><span class="s2">"./hillshade"</span>

<span class="c"># Process each file in the input folder</span>
find <span class="s2">"</span><span class="nv">$input_folder</span><span class="s2">"</span> <span class="nt">-type</span> f <span class="nt">-name</span> <span class="s2">"*.tif"</span> | <span class="k">while </span><span class="nb">read</span> <span class="nt">-r</span> input_file<span class="p">;</span> <span class="k">do
    </span><span class="nv">relative_path</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">input_file</span><span class="p">#</span><span class="nv">$input_folder</span><span class="p">/</span><span class="k">}</span><span class="s2">"</span>
    <span class="nb">echo</span> <span class="nv">$relative_path</span>

    <span class="nv">file_directory</span><span class="o">=</span><span class="si">$(</span><span class="nb">dirname</span> <span class="s2">"</span><span class="nv">$relative_path</span><span class="s2">"</span><span class="si">)</span>
    <span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"</span><span class="nv">$hillshade_output_folder</span><span class="s2">/</span><span class="nv">$file_directory</span><span class="s2">"</span>

    <span class="nv">base_name</span><span class="o">=</span><span class="si">$(</span><span class="nb">basename</span> <span class="s2">"</span><span class="nv">$input_file</span><span class="s2">"</span> .tif<span class="si">)</span>
    <span class="nv">output_file</span><span class="o">=</span><span class="s2">"</span><span class="nv">$hillshade_output_folder</span><span class="s2">/</span><span class="nv">$file_directory</span><span class="s2">/</span><span class="k">${</span><span class="nv">base_name</span><span class="k">}</span><span class="s2">.tif"</span>

    <span class="c"># Run GDAL command for hillshade</span>
    gdaldem hillshade <span class="nt">-s</span> 111120 <span class="nt">-z</span> 5 <span class="nt">-igor</span> <span class="nt">-compute_edges</span> <span class="s2">"</span><span class="nv">$input_file</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$output_file</span><span class="s2">"</span>
    <span class="nb">echo</span> <span class="s2">"Processed </span><span class="nv">$input_file</span><span class="s2"> -&gt; </span><span class="nv">$output_file</span><span class="s2">"</span>
<span class="k">done</span>
</code></pre></div></div>

<p>The main command performing the hillshade transformation is:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gdaldem hillshade <span class="nt">-s</span> 111120 <span class="nt">-z</span> 5 <span class="nt">-igor</span> <span class="nt">-compute_edges</span> <span class="s2">"</span><span class="nv">$input_file</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$output_file</span><span class="s2">"</span>
</code></pre></div></div>

<ul>
  <li>Scale <strong>111120</strong> is for DEM files in <strong>EPSG:4326</strong>. Omit if using metric coordinates.</li>
  <li>A Z-scale of 5 produces lighter shades.</li>
  <li>The <code class="language-plaintext highlighter-rouge">-igor</code> parameter provides subtle hillshading, ideal for blending with other layers.</li>
  <li><code class="language-plaintext highlighter-rouge">-compute_edges</code> is used to avoid grid lines due to missing values in some DEM files.</li>
</ul>

<h3 id="applying-colors-to-dem-files">Applying Colors to DEM Files</h3>
<p>Once you have contoured hillshading, apply a color ramp to bring the DEM data to life. My color palette is inspired by <a href="https://cartographicperspectives.org/index.php/journal/article/view/cp69-patterson-jenny/html">The Development and Rationale of Cross-blended Hypsometric Tints, by Patterson &amp; Jenny</a>, with minor adjustments.</p>

<p>To decide on color distribution, I generated a histogram of the overall data. For this, I created a VRT of all raw DEM files and calculated the histogram in QGIS:</p>

<p><img src="/assets/photos/dem-histogram-preview.png" alt="Image" /></p>

<p>Spikes in the histogram indicate where more color variation is needed to enhance detail. Here’s the final color ramp:</p>

<pre><code class="language-property">-100 112 137 199 255 
70 112 147 141 255 
200 120 159 152 255 
300 130 165 159 255 
700 145 177 171 255 
1200 180 192 180 255 
1400 212 201 180 255 
1600 212 184 163 255 
1800 212 193 179 255 
2200 212 207 204 255 
2400 220 220 220 255 
2800 235 235 237 255 
3500 245 245 245 255 
</code></pre>

<h1 id="geoserver-configuration">GeoServer Configuration</h1>
<h2 id="creating-geoserver-stores">Creating GeoServer Stores</h2>
<ul>
  <li>Upload raw and hillshaded GeoTIFF files to your GeoServer filesystem.</li>
  <li>Add <strong>indexer.properties</strong> files to the raw and hillshade layer folders for Image Mosaic plugin configuration.</li>
</ul>

<h4 id="indexerproperties">indexer.properties</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SuggestedFormat=org.geotools.gce.geotiff.GeoTiffFormat
Name=dem-raw-mosaic **OR** dem-hillshade-mosaic
SuggestedSPI=it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReaderSpi
Heterogeneous=false
HeterogeneousCRS=false
PropertyCollectors=CRSExtractorSPI(crs),ResolutionExtractorSPI(resolution)
Schema=*the_geom:Polygon,location:String,crs:String,resolution:String
MosaicCRS=EPSG\:4326 **CHANGE THIS IF NEEDED**
</code></pre></div></div>

<ul>
  <li>Create two ImageMosaic stores and publish their layers.</li>
</ul>

<h2 id="configuring-styles">Configuring Styles</h2>
<p>Create two styles in GeoServer.</p>

<h4 id="hillshade-style-ysld">Hillshade Style (YSLD)</h4>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">hillshade</span>
<span class="na">feature-styles</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">x-composite</span><span class="pi">:</span> <span class="s">multiply</span>
  <span class="na">rules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">title</span><span class="pi">:</span> <span class="s">raster</span>
    <span class="na">symbolizers</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">raster</span><span class="pi">:</span>
        <span class="na">opacity</span><span class="pi">:</span> <span class="m">1.0</span>
        <span class="na">channels</span><span class="pi">:</span>
          <span class="na">gray</span><span class="pi">:</span>
            <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">1'</span>
        <span class="na">channel-selection</span><span class="pi">:</span>
          <span class="na">gray-channel</span><span class="pi">:</span>
            <span class="na">source-channel-name</span><span class="pi">:</span> <span class="m">1</span>
        <span class="na">color-map</span><span class="pi">:</span>
          <span class="na">type</span><span class="pi">:</span> <span class="s">ramp</span>
          <span class="na">entries</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="pi">[</span> <span class="s2">"</span><span class="s">#000000"</span><span class="pi">,</span> <span class="nv">0.25</span><span class="pi">,</span> <span class="nv">0</span><span class="pi">,</span> <span class="s1">'</span><span class="s">'</span><span class="pi">]</span>
          <span class="pi">-</span> <span class="pi">[</span> <span class="s2">"</span><span class="s">#FFFFFF"</span><span class="pi">,</span> <span class="nv">1</span><span class="pi">,</span> <span class="nv">255</span><span class="pi">,</span> <span class="s1">'</span><span class="s">'</span><span class="pi">]</span>
</code></pre></div></div>

<h4 id="dem-style-sld">DEM Style (SLD)</h4>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;</span>
<span class="nt">&lt;StyledLayerDescriptor</span> <span class="na">version=</span><span class="s">"1.0.0"</span> 
  <span class="na">xsi:schemaLocation=</span><span class="s">"http://www.opengis.net/sld StyledLayerDescriptor.xsd"</span> 
  <span class="na">xmlns=</span><span class="s">"http://www.opengis.net/sld"</span> 
  <span class="na">xmlns:ogc=</span><span class="s">"http://www.opengis.net/ogc"</span> 
  <span class="na">xmlns:xlink=</span><span class="s">"http://www.opengis.net/xlink"</span> 
  <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;NamedLayer&gt;</span>
    <span class="nt">&lt;Name&gt;</span>Topographic DEM<span class="nt">&lt;/Name&gt;</span>
    <span class="nt">&lt;UserStyle&gt;</span>
      <span class="nt">&lt;Title&gt;</span>Topographic DEM<span class="nt">&lt;/Title&gt;</span>
      <span class="nt">&lt;Abstract&gt;</span>A topographic style for DEM data<span class="nt">&lt;/Abstract&gt;</span>
      <span class="nt">&lt;FeatureTypeStyle&gt;</span>
        <span class="nt">&lt;Rule&gt;</span>
          <span class="nt">&lt;RasterSymbolizer&gt;</span>
            <span class="nt">&lt;Opacity&gt;</span>1.0<span class="nt">&lt;/Opacity&gt;</span>
            <span class="nt">&lt;ColorMap</span> <span class="na">type=</span><span class="s">"interval"</span><span class="nt">&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#7089C7"</span> <span class="na">quantity=</span><span class="s">"-100"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#70938D"</span> <span class="na">quantity=</span><span class="s">"70"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#789F98"</span> <span class="na">quantity=</span><span class="s">"200"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#82A59F"</span> <span class="na">quantity=</span><span class="s">"300"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#91B1AB"</span> <span class="na">quantity=</span><span class="s">"700"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#B4C0B4"</span> <span class="na">quantity=</span><span class="s">"1200"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#D4C9B4"</span> <span class="na">quantity=</span><span class="s">"1400"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#D4B8A3"</span> <span class="na">quantity=</span><span class="s">"1600"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#D4C1B3"</span> <span class="na">quantity=</span><span class="s">"1800"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#D4CFCC"</span> <span class="na">quantity=</span><span class="s">"2200"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#DCDCDC"</span> <span class="na">quantity=</span><span class="s">"2400"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#EBEBED"</span> <span class="na">quantity=</span><span class="s">"2800"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
              <span class="nt">&lt;ColorMapEntry</span> <span class="na">color=</span><span class="s">"#F5F5F5"</span> <span class="na">quantity=</span><span class="s">"3500"</span> <span class="na">opacity=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
            <span class="nt">&lt;/ColorMap&gt;</span>
          <span class="nt">&lt;/RasterSymbolizer&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
      <span class="nt">&lt;/FeatureTypeStyle&gt;</span>
    <span class="nt">&lt;/UserStyle&gt;</span>
  <span class="nt">&lt;/NamedLayer&gt;</span>
<span class="nt">&lt;/StyledLayerDescriptor&gt;</span>
</code></pre></div></div>

<h2 id="creating-layer-groups">Creating Layer Groups</h2>
<p>Create the final layer group with the following layer order:</p>
<ol>
  <li><strong>dem-raw-mosaic</strong> with the <strong>dem</strong> style.</li>
  <li><strong>dem-hillshade-mosaic</strong> with the <strong>hillshade</strong> style.</li>
</ol>

<h2 id="next-steps-and-final-thoughts">Next Steps and Final Thoughts</h2>
<p>Once you have the layer group, it is ready to be served!
If you found this guide helpful, consider trying it out with other regions or experimenting with different color ramps and hillshade parameters to see the variety of effects you can achieve. Whether you’re mapping for data analysis, design, or just for fun, hillshading opens up a world of possibilities in GIS visualization.</p>]]></content><author><name></name></author><category term="gis" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Spring Cloud: Exception handling with custom body modifiers in the route!</title><link href="/backend/2024/09/19/microservice-exceptions-cloud-gateway.html" rel="alternate" type="text/html" title="Spring Cloud: Exception handling with custom body modifiers in the route!" /><published>2024-09-19T00:00:00+00:00</published><updated>2024-09-19T00:00:00+00:00</updated><id>/backend/2024/09/19/microservice-exceptions-cloud-gateway</id><content type="html" xml:base="/backend/2024/09/19/microservice-exceptions-cloud-gateway.html"><![CDATA[<h3 id="introduction">Introduction</h3>

<p>In the project I am working on, we have a microservice architecture. Over these microservices, a gateway sits to protect and handle all calls. In most cases, the gateway merely proxies requests, with no business logic involved. However, there are cases where we need to enrich the result of one microservice call by combining it with the result of another. To achieve this, we use the <code class="language-plaintext highlighter-rouge">modifyResponseBody</code> function in the route configuration.</p>

<p>Here’s an example of a typical route setup:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="n">route</span>
    <span class="o">.</span><span class="na">path</span><span class="o">(</span><span class="n">path</span><span class="o">)</span>
    <span class="o">.</span><span class="na">and</span><span class="o">()</span>
    <span class="o">.</span><span class="na">method</span><span class="o">(</span><span class="n">method</span><span class="o">)</span>
    <span class="o">.</span><span class="na">filters</span><span class="o">(</span><span class="n">f</span> <span class="o">-&gt;</span> <span class="o">{</span>
        <span class="n">f</span><span class="o">.</span><span class="na">tokenRelay</span><span class="o">();</span>
        <span class="n">f</span><span class="o">.</span><span class="na">modifyResponseBody</span><span class="o">(</span><span class="n">inClass</span><span class="o">,</span> <span class="n">outClass</span><span class="o">,</span> <span class="n">filter</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">f</span><span class="o">;</span>
    <span class="o">}).</span><span class="na">uri</span><span class="o">(</span><span class="n">serviceURL</span><span class="o">);</span>
</code></pre></div></div>

<p>In this setup, <code class="language-plaintext highlighter-rouge">filter</code> refers to a class that implements the <code class="language-plaintext highlighter-rouge">RewriteFunction&lt;inClass, outClass&gt;</code> interface, which contains the following method:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Publisher</span><span class="o">&lt;</span><span class="no">O</span><span class="o">&gt;</span> <span class="nf">apply</span><span class="o">(</span><span class="nc">ServerWebExchange</span> <span class="n">serverWebExchange</span><span class="o">,</span> <span class="no">I</span> <span class="n">inObject</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
</code></pre></div></div>

<p>Everything works well until an exception occurs in the microservice returning the <code class="language-plaintext highlighter-rouge">inObject</code>. When an error response is returned instead of the expected object, the gateway throws an error trying to convert the error response into the expected type.</p>

<h3 id="the-problem">The Problem</h3>

<p>When an exception is returned from the microservice, the gateway struggles to handle it correctly, leading to a failure when it tries to convert the error response. This happens because the body modifier expects a specific class (such as <code class="language-plaintext highlighter-rouge">inClass</code>), but the error response does not match this structure.</p>

<h3 id="the-solution">The Solution</h3>

<p>To solve this issue, we need to ensure that the response from the microservice is valid before attempting to map the body to the desired class. I considered two potential approaches:</p>

<h4 id="1-using-filters">1. Using Filters</h4>

<p>Spring Cloud has post-filters that trigger after receiving a response from the microservice. My initial idea was to use a filter to break the chain of body modifiers or other filters if the response is an exception. However, after some research, I found that it wasn’t possible to break the route chain in post-filters.</p>

<h4 id="2-creating-an-abstractresponsebodymodifier-class">2. Creating an <code class="language-plaintext highlighter-rouge">AbstractResponseBodyModifier</code> Class</h4>

<p>The second approach involved creating a base <code class="language-plaintext highlighter-rouge">AbstractResponseBodyModifier</code> class. This class checks if the response is a successful one (i.e., HTTP 2xx) before converting the response into the desired custom object. If the response is successful, we proceed with mapping the response body. Otherwise, we return the response as is without modifying it.</p>

<p>Here’s an example of the implementation:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractResponseBodyModifier</span><span class="o">&lt;</span><span class="no">I</span><span class="o">,</span> <span class="no">O</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="nc">RewriteFunction</span><span class="o">&lt;</span><span class="nc">DataBuffer</span><span class="o">,</span> <span class="nc">DataBuffer</span><span class="o">&gt;</span> <span class="o">{</span>

    <span class="c1">// field definitions and constructors</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">Publisher</span><span class="o">&lt;</span><span class="nc">DataBuffer</span><span class="o">&gt;</span> <span class="nf">apply</span><span class="o">(</span><span class="nc">ServerWebExchange</span> <span class="n">serverWebExchange</span><span class="o">,</span> <span class="nc">DataBuffer</span> <span class="n">dataBuffer</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">logger</span><span class="o">.</span><span class="na">trace</span><span class="o">(</span><span class="s">"Applying filters to response body..."</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">dataBuffer</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// When body is empty, data buffer object is null</span>
            <span class="k">return</span> <span class="nc">Mono</span><span class="o">.</span><span class="na">empty</span><span class="o">();</span>
        <span class="o">}</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">serverWebExchange</span><span class="o">.</span><span class="na">getResponse</span><span class="o">().</span><span class="na">getStatusCode</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">serverWebExchange</span><span class="o">.</span><span class="na">getResponse</span><span class="o">().</span><span class="na">getStatusCode</span><span class="o">().</span><span class="na">is2xxSuccessful</span><span class="o">())</span> <span class="o">{</span>
            <span class="kt">byte</span><span class="o">[]</span> <span class="n">sourceBytes</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="n">dataBuffer</span><span class="o">.</span><span class="na">readableByteCount</span><span class="o">()];</span>
            <span class="n">dataBuffer</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">sourceBytes</span><span class="o">);</span>
            <span class="nc">DataBufferUtils</span><span class="o">.</span><span class="na">release</span><span class="o">(</span><span class="n">dataBuffer</span><span class="o">);</span>
            <span class="no">I</span> <span class="n">object</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
            <span class="k">try</span> <span class="o">{</span>
                <span class="n">object</span> <span class="o">=</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">readValue</span><span class="o">(</span><span class="n">sourceBytes</span><span class="o">,</span> <span class="n">inputType</span><span class="o">);</span>
            <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">return</span> <span class="nc">Mono</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
            <span class="o">}</span>
            <span class="nc">Mono</span><span class="o">&lt;</span><span class="no">O</span><span class="o">&gt;</span> <span class="n">modifiedResponse</span> <span class="o">=</span> <span class="n">modifyResponseBody</span><span class="o">(</span><span class="n">serverWebExchange</span><span class="o">,</span> <span class="n">object</span><span class="o">);</span>
            <span class="k">return</span> <span class="n">modifiedResponse</span><span class="o">.</span><span class="na">handle</span><span class="o">((</span><span class="n">e</span><span class="o">,</span> <span class="n">sink</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="o">{</span>
                <span class="k">try</span> <span class="o">{</span>
                    <span class="n">sink</span><span class="o">.</span><span class="na">next</span><span class="o">(</span><span class="n">rewriterUtils</span><span class="o">.</span><span class="na">toDatabuffer</span><span class="o">(</span><span class="n">e</span><span class="o">));</span>
                <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">JsonProcessingException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">sink</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="k">new</span> <span class="nc">RuntimeException</span><span class="o">(</span><span class="n">ex</span><span class="o">));</span>
                <span class="o">}</span>
            <span class="o">});</span>
        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
            <span class="n">logger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Done applying filters to response body, response status was not successful"</span><span class="o">);</span>
            <span class="k">return</span> <span class="nc">Mono</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="n">dataBuffer</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="cm">/**
     * Abstract method for modifying response body
     *
     * @param serverWebExchange
     * @param responseDataBuffer
     * @return
     */</span>
    <span class="kd">public</span> <span class="kd">abstract</span> <span class="nc">Mono</span><span class="o">&lt;</span><span class="no">O</span><span class="o">&gt;</span> <span class="nf">modifyResponseBody</span><span class="o">(</span><span class="nc">ServerWebExchange</span> <span class="n">serverWebExchange</span><span class="o">,</span> <span class="no">I</span> <span class="n">responseDataBuffer</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>This solution ensures that before attempting to modify the response body, we first verify if the HTTP status code is 2xx. If the status code indicates an error, the original response is returned without modification.</p>

<h3 id="modifying-the-route-configuration">Modifying the Route Configuration</h3>

<p>Once we implement the <code class="language-plaintext highlighter-rouge">AbstractResponseBodyModifier</code>, we need to update the route configuration to tell the gateway that we are working with <code class="language-plaintext highlighter-rouge">DataBuffer</code> objects, not custom objects.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span><span class="o">.</span><span class="na">modifyResponseBody</span><span class="o">(</span><span class="nc">DataBuffer</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">DataBuffer</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">filter</span><span class="o">);</span>
</code></pre></div></div>

<p>This change informs the gateway that it doesn’t need to perform automatic mapping, as our custom modifier will handle the conversion manually.</p>

<h3 id="the-downside">The Downside</h3>

<p>A potential downside of this approach is that we take over the responsibility of mapping objects manually. This is fine as long as we are confident about the structure and content of the expected results. However, if the structure of the responses changes, the custom mapping logic will need to be updated accordingly.</p>

<h3 id="conclusion">Conclusion</h3>

<p>Handling microservice exceptions in Spring Cloud Gateway when using body modifiers can be tricky, but by creating a custom <code class="language-plaintext highlighter-rouge">AbstractResponseBodyModifier</code>, we can ensure that error responses are handled gracefully without breaking the response pipeline. This approach keeps the gateway lightweight and ensures that business logic is managed in the microservices themselves.</p>

<p>I hope this solution helps anyone facing similar challenges in their Spring Cloud Gateway setups!</p>

<p>#SpringCloudGateway #Microservices #ExceptionHandling #BodyModifiers #RewriteFunction #AbstractResponseBodyModifier #SpringBoot #Java #GatewayArchitecture #ErrorHandling #APIGateway #SpringCloud #WebFlux</p>]]></content><author><name></name></author><category term="backend" /><summary type="html"><![CDATA[Introduction]]></summary></entry></feed>