Skip to content

3. اقدامات: پاسخ

بیایید معماری یک برنامه Spring MVC را در نظر بگیریم:

در این فصل، ما فرآیند مسیریابی درخواست [1] به کنترلر و اکشن [2a] که آن را پردازش می‌کند را بررسی می‌کنیم – سازوکاری که به آن مسیریابی گفته می‌شود. ما همچنین پاسخ‌های مختلف ([3]) را که یک اکشن می‌تواند به مرورگر بازگرداند، تشریح می‌کنیم. این پاسخ‌ها ممکن است چیزی غیر از یک نمای V ([4b]) باشند.

3.1. پروژه جدید

ما در حال ایجاد یک پروژه جدید Spring به نام MVC هستیم:

  • در [1-2]، ما یک پروژه جدید مبتنی بر Spring Boot ایجاد می‌کنیم؛
  • در [3]، نام پروژه Maven؛
  • در [4]، گروه Maven که خروجی کامپایل پروژه در آن قرار خواهد گرفت؛
  • [5]، نام اختصاص‌داده‌شده به خروجی کامپایل؛
  • در [6]، توضیحی درباره پروژه؛
  • [7]، بسته‌ای که کلاس قابل اجرا (executable) پروژه در آن قرار می‌گیرد؛
  • در [8]، ماهیت پروژه مشخص می‌شود. این یک پروژه وب با ویوهای Thymeleaf است. در اینجا می‌توانیم تمام وابستگی‌های آماده استفاده Maven را که توسط پروژه Spring Boot فراهم شده‌اند، مشاهده کنیم؛
  • در [9]، مشخص می‌کنیم که خروجی ساخت Maven به جای WAR، به صورت یک آرشیو JAR بسته‌بندی شود. سپس پروژه از یک سرور Tomcat داخلی استفاده خواهد کرد که در وابستگی‌های آن گنجانده خواهد شد؛
  • در [10]، به مرحله بعدی جادوگر می‌رویم؛
  • در [11]، مسیر پروژه را مشخص کنید؛
  • در [12]، پروژهٔ تولیدشده؛
  • در [14-15]، بسته را در [istia.st.springmvc] نام‌گذاری مجدد کنید؛
  • به [16]، نام بسته جدید؛
  • به [17]، پروژهٔ جدید؛

اکنون یک کلاس جدید ایجاد می‌کنیم؛

  • در [1-3]، یک کلاس جدید ایجاد می‌کنیم؛
  • در [5] آن را تخصیص می‌دهیم و در [4] بستهٔ آن را مشخص می‌کنیم؛
  • در [6]، پروژه جدید؛

کلاس در حال حاضر به شرح زیر است:


package istia.st.springmvc;

public class ActionsController {

}

ما این کد را به شرح زیر به‌روزرسانی می‌کنیم:


package istia.st.springmvc;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class ActionsController {

}
  • خط ۶: تگ [@RestController] دو چیز را نشان می‌دهد:
    • که کلاس [ActionsController]، که به این صورت برچسب‌گذاری شده است، یک کنترلر Spring MVC است و بنابراین حاوی اقداماتی است که درخواست‌های کلاینت URL را مدیریت می‌کنند؛
    • که نتیجه این عملیات به کلاینت ارسال می‌شود؛

آنوتیشن دیگر، [@Controller]، که با آن مواجه شده‌ایم، متفاوت است: متدهای یک کنترلر که به این شکل آنوتیشن شده‌اند، نام ویوی نمایش داده شده را برمی‌گردانند. سپس ترکیب این ویو و مدلی که توسط متد برای آن ویو ساخته شده است، پاسخ ارسالی به کلاینت را فراهم می‌کند.

تغییر در ساختار پروژه ما مستلزم تغییری در پیکربندی پروژه ماست:

  

کلاس [Application] به شرح زیر تکامل می‌یابد:


package istia.st.springmvc.main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({"istia.st.springmvc.controllers"})
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  • خط ۹: آناوتیشن [ComponentScan] یک آرایه از نام بسته‌ها را به عنوان پارامتر می‌پذیرد که Spring Boot باید در آن به دنبال کامپوننت‌های Spring بگردد. در اینجا، ما بسته [istia.st.springmvc.controllers] را به این آرایه اضافه می‌کنیم تا کنترلری که با [@RestController] آناوت شده است، پیدا شود؛

ما در داخل کنترلر اکشن‌های مختلفی ایجاد خواهیم کرد تا ویژگی‌های اصلی آن‌ها را نشان دهیم. ابتدا به انواع مختلف پاسخ‌های ممکن از یک اکشن در یک برنامه بدون ویو می‌پردازیم.

3.2. [/a01, /a02] – Hello world

اولین اکشن ما به شرح زیر خواهد بود:


@RestController
public class ActionsController {
    // ----------------------- سلام دنیا ------------------------
    @RequestMapping(value = "/a01", method = RequestMethod.GET)
    public String a01() {
        return "Greetings from Spring Boot!";
    }
}
  • خط ۴: تگ [RequestMapping] درخواست رسیدگی‌شده توسط اکشن دارای تگ را مشخص می‌کند:
    • ویژگی [value] همان URL در حال پردازش است،
    • ویژگی [method] روش پذیرفته‌شده را مشخص می‌کند؛

بنابراین، متد [a01] درخواست HTTP [GET /a01] را پردازش می‌کند.

  • خط ۵: متد [a01] نوع [String] را برمی‌گرداند که همان‌طور که هست به کلاینت ارسال می‌شود؛
  • خط ۶: رشته بازگردانده‌شده؛

بیایید برنامه را همانطور که قبلاً چندین بار اجرا کرده‌ایم، و سپس با کلاینت [Advanced Rest Client] اجرا کنیم، ما با استفاده از GET و [1-2]، URL و [/a01] را درخواست می‌کنیم:

  • در [3]، پاسخ سرور؛
  • به [4]، سربرگ‌های HTTP از پاسخ. می‌توانیم ببینیم که از رمزگذاری [ISO-8859-1] استفاده شده است. ممکن است رمزگذاری UTF-8 را ترجیح دهیم. این را می‌توان پیکربندی کرد؛
  • با استفاده از [5]، همان URL را با مرورگر کروم درخواست می‌کنیم؛

ما اقدام زیر، [/a02]، را به کنترلر [ActionsController] اضافه می‌کنیم (این بدان معناست که URL و متدی که آن را مدیریت می‌کند گاهی ممکن است تحت نام اقدام با هم اشتباه گرفته شوند):


    // ----------------------- کاراکترهای دارای نقطه - UTF8 ------------------------
    @RequestMapping(value = "/a02", method = RequestMethod.GET, produces="text/plain;charset=UTF-8")
    public String a02() {
        return "caractères accentués : éèàôûî";
}
  • خط ۲: ویژگی [produces="text/plain;charset=UTF-8"] نشان می‌دهد که این اکشن یک جریان متنی را با کاراکترهایی که با فرمت [UTF-8] رمزگذاری شده‌اند، ارسال می‌کند. این فرمت به ویژه امکان استفاده از کاراکترهای دارای نشانه (accented characters) را فراهم می‌کند؛

برای در نظر گرفتن این اقدام جدید، باید برنامه را مجدداً راه‌اندازی کنیم:

 

نتیجه به شرح زیر است:

  • در [1]، می‌توانیم ماهیت سند ارسال‌شده توسط سرور را مشاهده کنیم؛
  • در [2-3]، کاراکترهای دارای علامت diakritique به درستی نمایش داده می‌شوند؛

3.3. [/a03]: رندر کردن یک جریان XML

ما اقدام زیر را اضافه می‌کنیم: [/a03]:


    // ----------------------- text/xml ------------------------
    @RequestMapping(value = "/a03", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
    public String a03() {
        String greeting = "<greetings><greeting>Greetings from Spring Boot!</greeting></greetings>";
        return greeting;
}
  • خط ۲: ویژگی [produces="text/xml;charset=UTF-8"] نشان می‌دهد که این عمل یک جریان XML را با کاراکترهای رمزگذاری‌شده در قالب [UTF-8] ارسال می‌کند؛

هنگام اجرا، این خروجی زیر را تولید می‌کند:

  • در [1]، هدر HTTP مشخص می‌کند که سند ارسالی در HTML است؛
  • در [2]، مرورگر کروم از این اطلاعات برای قالب‌بندی متن دریافتی XML استفاده می‌کند؛

شایان ذکر است که در کروم، می‌توانید مبادلات HTTP بین کلاینت و سرور را در پنجره ابزارهای توسعه‌دهنده (Ctrl+Shift+I) مشاهده کنید:

Image

از این پس، ما به طور سیستماتیک از مبادلات HTTP بین کلاینت و سرور اسکرین‌شات نخواهیم گرفت. گاهی اوقات، ما صرفاً متن این مبادلات را نقل خواهیم کرد.

3.4. [/a04, /a05]: یک جریان jSON را بازگردانید

ما اقدام زیر را اضافه می‌کنیم، [/a04]:


    // ----------------------- تولید jSON ------------------------
    @RequestMapping(value = "/a04", method = RequestMethod.GET)
    public Map<String, Object> a04() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("1", "un");
        map.put("2", new int[] { 4, 5 });
        return map;
}
  • خط ۳: عمل یک نوع [Map]، یک دیکشنری، را برمی‌گرداند. به یاد داشته باشید که با یک کنترل‌کننده از نوع [@RestController]، نتیجهٔ اقدام، پاسخی است که به کلاینت ارسال می‌شود. از آنجا که پروتکل HTTP یک پروتکل برای تبادل خطوط متن است، پاسخ کلاینت باید به یک رشتهٔ کاراکتری سریالیزه شود. برای این کار، Spring MVC از convertersهای مختلف [Objet <---> chaîne de caractères] استفاده می‌کند. ارتباط یک شیء خاص با یک converter از طریق پیکربندی انجام می‌شود. در اینجا، پیکربندی خودکار Spring Boot وابستگی‌های پروژه را بررسی خواهد کرد:
 

وابستگی‌های Jackson که در بالا فهرست شده‌اند، کتابخانه‌هایی برای سریالی‌سازی و دِسریالی‌سازی اشیاء به رشته‌های jSON هستند. سپس Spring Boot از این کتابخانه‌ها برای سریالی‌سازی و دِسریالی‌سازی اشیاء بازگشتی از اکشن‌ها استفاده خواهد کرد. یک مثال از کد جاوا برای سریالی‌سازی و دِسریالی‌سازی اشیاء جاوا در jSON را می‌توان در بخش 9.7 یافت.

توجه کنید که در خط ۲ نوع پاسخ ارسالی را مشخص نکرده‌ایم. اکنون به نوع پیش‌فرض ارسالی می‌پردازیم.

نتایج در کروم به شرح زیر است: [1-3]:

حال بیایید اقدام زیر را اضافه کنیم: [/a05]:


    // ----------------------- تولید jSON - 2 ------------------------
    @RequestMapping(value = "/a05", method = RequestMethod.GET)
    public Personne a05() {
        return new Personne(1,"carole",45);
}

کلاس [Personne] به شرح زیر است:

  

package istia.st.sprinmvc.models;

public class Personne {

    // شناسگر
    private Integer id;
    // نام خانوادگی
    private String nom;
    // سن
    private int age;

    // سازنده‌ها
    public Personne() {

    }

    public Personne(String nom, int age) {
        this.nom = nom;
        this.age = age;
    }

    public Personne(Integer id, String nom, int age) {
        this(nom, age);
        this.id = id;
    }

    @Override
    public String toString() {
        return String.format("[id=%s, nom=%s,  age=%d]", id, nom, age);
    }

    // گیرنده‌ها و تنظیم‌کننده‌ها
...
}

اجرای کد نتایج زیر را تولید می‌کند:

  • در [1]، سرور نشان می‌دهد که سند در حال ارسال، jSON است؛
  • در [2]، سند jSON دریافت می‌شود؛

3.5. [/a06]: بازگرداندن یک جریان خالی

ما اقدام زیر را اضافه می‌کنیم، [/a06]:


    // ----------------------- بازگرداندن یک جریان خالی ------------------------
    @RequestMapping(value = "/a06")
    public void a06() {
}
  • خط ۳، عمل [/a06] هیچ چیزی را بازنمی‌گرداند. سپس Spring MVC یک پاسخ خالی به کلاینت تولید خواهد کرد؛

اجرا نتایج زیر را تولید می‌کند:

 

در بالا، ویژگی HTTP در پاسخ نشان می‌دهد که سرور در حال ارسال یک سند خالی است.

3.6. [/a07, /a08, /a09]: ماهیت جریان با [Content-Type]

ما اقدام زیر را اضافه می‌کنیم: [/a07]:


    // ----------------------- text/html ------------------------
    @RequestMapping(value = "/a07", method = RequestMethod.GET, produces = "text/html;charset=UTF-8")
    public String a07() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • خط ۲: عمل [/a07] یک جریان HTML [text/html] را بازمی‌گرداند؛
  • خط ۴: یک رشته HTML;

اجرا نتایج زیر را تولید می‌کند:

  • در [1]، می‌بینیم که کروم تگ HTML را تفسیر کرده است، که محتوای خود را با حروف درشت نمایش می‌دهد؛

حالا بیایید همین کار را با اقدام زیر انجام دهیم، [/a08]:


    // ----------------------- نتیجه HTML در text/plain ------------------------
    @RequestMapping(value = "/a08", method = RequestMethod.GET, produces = "text/plain;charset=UTF-8")
    public String a08() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • خط ۲: پاسخ این عمل از نوع [text/plain] است؛

نتایج به شرح زیر است:

  • در [1]، کروم تگ از HTML را تفسیر نکرد، زیرا سرور به آن اطلاع داد که در حال ارسال یک جریان [text/plain] [2] است؛

بیایید دوباره با اقدام [/a09] زیر، مشابه آن را امتحان کنیم:


    // ----------------------- HTML نتیجه به صورت text/xml ------------------------
    @RequestMapping(value = "/a09", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
    public String a09() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • خط ۲: ما یک جریان از نوع [text/xml] ارسال می‌کنیم؛

نتایج به شرح زیر است:

  • در [1]، کروم تگ را تفسیر نکرد زیرا سرور به آن گفته بود که در حال ارسال یک جریان [text/xml] [2] است. بنابراین، تگ را به‌عنوان یک تگ XML در نظر گرفت؛

آنچه می‌توانیم از این مثال‌ها برداشت کنیم اهمیت هدر HTTP [Content-Type] در پاسخ سرور است. مرورگر از این هدر برای تعیین نحوه تفسیر سند دریافتی استفاده می‌کند؛

3.7. [/a10, /a11, /a12]: هدایت مشتری

ما یک کنترلر جدید [RedirectController] ایجاد می‌کنیم:

 

کد برای [RedirectCntroller] در حال حاضر به شرح زیر خواهد بود:


package istia.st.springmvc.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class RedirectController {
}
  • خط ۷: ما از انوتیشن [@Controller] استفاده می‌کنیم، که به این معنی است که از این پس، به طور پیش‌فرض، نوع [String] نتیجه اکشن به نام یک اکشن یا یک ویو ارجاع می‌دهد؛

ما اقدام زیر را ایجاد می‌کنیم، [/a10]:


    // ------------ هدایت به یک اقدام شخص ثالث -----------------------
    @RequestMapping(value = "/a10", method = RequestMethod.GET)
    public String a10() {
        return "a01";
}
  • خط ۴: ما مقدار 'a01' را به عنوان نتیجه بازمی‌گردانیم که نام یک اکشن است. این اکشن سپس پاسخ را برای کلاینت ارسال می‌کند؛

در اینجا یک مثال آورده شده است:

  • در [2]، ما جریان داده را از اقدام [/a01] دریافت کردیم؛
  • در [3]، مرورگر URL را از اقدام [/a10] نمایش می‌دهد؛

اکنون اقدام زیر را ایجاد خواهیم کرد، [/a11]:


    // ------------ هدایت موقت 302 به یک اقدام شخص ثالث -----------------------
    @RequestMapping(value = "/a11", method = RequestMethod.GET)
    public String a11() {
        return "redirect:/a01";
}

نتایج زیر را به دست می‌آوریم:

  • در لاگ‌های کروم برای [1-2]، می‌توانیم دو درخواست را مشاهده کنیم: یکی به [/a11] و دیگری به [/a01]؛
  • در [3]، سرور با کدی [302] پاسخ می‌دهد که به مرورگر کلاینت دستور می‌دهد بهURL که توسط هدرهای HTTP، [Location:] و [4] مشخص شده است. کد [302] یک کد تغییر مسیر موقت است؛

مرورگر سپس یک درخواست دوم به URL هدایت URL ارسال می‌کند:

  • به [5]، درخواست دوم کلاینت؛
  • به [6]، مرورگر مشتری URL را از درخواست هدایت نمایش می‌دهد؛

ممکن است بخواهید یک هدایت دائمی را مشخص کنید، در این صورت باید هدر زیر HTTP را به کلاینت ارسال کنید:

HTTP/1.1 301 Moved Permanently 

که نشان می‌دهد این تغییر مسیر دائمی است. این تمایز بین تغییر مسیر موقت (302) و دائمی (301) توسط برخی موتورهای جستجو در نظر گرفته می‌شود.

ما عمل [/a12] را می‌نویسیم که این هدایت دائمی را انجام خواهد داد:


    // ------------ هدایت دائمی ۳۰۱ به یک اقدام شخص ثالث----------------
    @RequestMapping(value = "/a12", method = RequestMethod.GET)
    public void a12(HttpServletResponse response) {
        response.setStatus(301);
        response.addHeader("Location", "/a01");
}
  • خط ۳: ما به Spring دستور می‌دهیم تا شیء [HttpServletResponse] را تزریق کند، که پاسخ ارسال‌شده به کلاینت را در خود جای داده است؛
  • خط ۴: ما [status] را برای پاسخ، [301] را برای هدر و HTTP را برای ... تنظیم می‌کنیم:
HTTP/1.1 301 Moved Permanently
  • خط ۵: سربرگ زیر HTTP به‌صورت دستی ایجاد می‌شود:
Location: /a01 

که هدر هدایت URL است.

اجرای کد نتایج زیر را تولید می‌کند:

نکته کلیدی این مثال نحوه انجام موارد زیر است:

  • ایجاد وضعیت پاسخ HTTP؛
  • اضافه کردن یک هدر HTTP در پاسخ؛

3.8. [/a13]: تولید پاسخ کامل

می‌توان کنترل کاملی بر پاسخ داشت، همان‌طور که در عمل زیر از کلاس [ResponsesController] نشان داده شده است:

  

    // ----------------------- تولید کامل پاسخ ------------------------
    @RequestMapping(value = "/a13")
    public void a13(HttpServletResponse response) throws IOException {
        response.setStatus(666);
        response.addHeader("header1", "qq chose");
        response.addHeader("Content-Type", "text/html;charset=UTF-8");
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        response.getWriter().write(greeting);
}
  • خط ۳: نتیجه این عمل [void] است. در این مورد، برای ارسال یک پاسخ غیرخالی به کلاینت، باید از شیء [HttpServletResponse response] که توسط Spring MVC فراهم شده است، استفاده کنید؛
  • خط ۴: به پاسخ وضعیتی داده می‌شود که توسط کلاینت شناسایی نخواهد شد؛
  • خط ۵: یک هدر HTTP اضافه می‌شود که توسط کلاینت شناسایی نخواهد شد؛
  • خط ۶: یک هدر HTTP [Content-Type] برای مشخص کردن نوع استریم ارسالی اضافه کنید، در این مورد HTML;
  • خطوط ۷–۸: سندی که پس از سربرگ‌های HTTP در پاسخ می‌آید؛

نتایج به شرح زیر است:

  • در [1]، می‌توانیم عناصر پاسخ خود را تشخیص دهیم؛
  • در [2-3]، می‌توانیم ببینیم که کروم این واقعیت را نادیده گرفته است که:
    • وضعیت پاسخ HTTP یک وضعیت شناخته‌شده HTTP نبود،
    • که هدر [header1] یک هدر شناخته‌شده نبود HTTP;

اگر کلاینت یک مرورگر نباشد بلکه یک کلاینت برنامه‌نویسی‌شده باشد، شما آزاد هستید از هر وضعیت و هدر دلخواهی استفاده کنید.