<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.2">Jekyll</generator><link href="https://ouyi.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ouyi.github.io/" rel="alternate" type="text/html" /><updated>2022-04-21T08:50:27+00:00</updated><id>https://ouyi.github.io/feed.xml</id><title type="html">Memory Spills</title><subtitle>Another geek blog on software engineering, data processing, and cloud computing.</subtitle><author><name>Yi Ou</name></author><entry><title type="html">AWS IAM core concepts explained</title><link href="https://ouyi.github.io/post/2020/12/18/aws-iam-concepts.html" rel="alternate" type="text/html" title="AWS IAM core concepts explained" /><published>2020-12-18T19:00:00+00:00</published><updated>2020-12-19T21:05:20+00:00</updated><id>https://ouyi.github.io/post/2020/12/18/aws-iam-concepts</id><content type="html" xml:base="https://ouyi.github.io/post/2020/12/18/aws-iam-concepts.html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#what-is-iam&quot; id=&quot;markdown-toc-what-is-iam&quot;&gt;What is IAM&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-core-concepts&quot; id=&quot;markdown-toc-the-core-concepts&quot;&gt;The core concepts&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#policies&quot; id=&quot;markdown-toc-policies&quot;&gt;Policies&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#users&quot; id=&quot;markdown-toc-users&quot;&gt;Users&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#groups&quot; id=&quot;markdown-toc-groups&quot;&gt;Groups&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#roles&quot; id=&quot;markdown-toc-roles&quot;&gt;Roles&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#instance-profiles&quot; id=&quot;markdown-toc-instance-profiles&quot;&gt;Instance profiles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-they-play-together&quot; id=&quot;markdown-toc-how-they-play-together&quot;&gt;How they play together&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#summary&quot; id=&quot;markdown-toc-summary&quot;&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;em&gt;What are policies in IAM?&lt;/em&gt;&lt;/li&gt;
    &lt;li&gt;&lt;em&gt;What are the differences between groups and roles?&lt;/em&gt;&lt;/li&gt;
    &lt;li&gt;&lt;em&gt;What is an instance profile and when shall it be used?&lt;/em&gt;&lt;/li&gt;
    &lt;li&gt;&lt;em&gt;What are the differences between roles and instance profiles?&lt;/em&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;These are some of the questions I had while working on a side project on AWS. It turns out that &lt;em&gt;users&lt;/em&gt;, &lt;em&gt;groups&lt;/em&gt;, &lt;em&gt;roles&lt;/em&gt;, and &lt;em&gt;policies&lt;/em&gt; are core concepts of AWS &lt;em&gt;Identity and Access Management (IAM)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;IAM is crucial to the security of applications and infrastructures built on AWS. In this post, I would like to share my understanding of IAM and its core concepts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use policies to define permissions&lt;/li&gt;
  &lt;li&gt;To grant long-term permissions to users, attach the policies to groups&lt;/li&gt;
  &lt;li&gt;To grant short-term permissions to users or services, attach the policies to roles&lt;/li&gt;
  &lt;li&gt;To pass a role to EC2 instances, use a corresponding instance profile&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-is-iam&quot;&gt;What is IAM&lt;/h2&gt;

&lt;p&gt;IAM is an AWS security feature for managing access to AWS services and resources. These services and resources are not only accessed by &lt;em&gt;human users&lt;/em&gt;, but also by &lt;em&gt;non-human entities&lt;/em&gt; such as other services, instances, or applications. Therefore the term &lt;em&gt;security principal&lt;/em&gt; refers to either a human user or such a non-human entity. AWS IAM covers both aspects of access control: authentication and authorization.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Authentication&lt;/em&gt; validates principals are who they claim to be, and&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Authorization&lt;/em&gt; gives the authenticated principals permissions they are supposed to have and denies any other accesses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-core-concepts&quot;&gt;The core concepts&lt;/h2&gt;

&lt;p&gt;For me it was helpful to know for each of the IAM concepts, whether it is relevant to human users or nonhuman entities. This is summarized in the following table:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Concept&lt;/th&gt;
      &lt;th&gt;Human users&lt;/th&gt;
      &lt;th&gt;Nonhuman entities&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Policies&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Users&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Groups&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Roles&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;policies&quot;&gt;Policies&lt;/h3&gt;

&lt;p&gt;In IAM, permissions are defined in policies. A &lt;em&gt;policy&lt;/em&gt; specifies what kind of accesses are allowed for which resources under what conditions. Policies can be attached to users, groups, and roles&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. Attaching a policy to a user gives the user permissions specified in the policy. The implications of attaching a policy to groups or roles are discussed later in this post.&lt;/p&gt;

&lt;p&gt;The example&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; below is a policy that allows an IAM user to start or stop EC2 instances, but only for instances having an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Owner&lt;/code&gt; tag with the value matching the user’s username. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DescribeInstances&lt;/code&gt; permissions are needed to complete this action on the AWS console.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2012-10-17&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Statement&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Effect&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Allow&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ec2:StartInstances&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ec2:StopInstances&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Resource&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;arn:aws:ec2:*:*:instance/*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Condition&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;StringEquals&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ec2:ResourceTag/Owner&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;${aws:username}&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Effect&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Allow&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ec2:DescribeInstances&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Resource&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;users&quot;&gt;Users&lt;/h3&gt;

&lt;p&gt;An &lt;em&gt;IAM user&lt;/em&gt; is a person accessing AWS services and resources. Users directly interact with AWS services and have &lt;em&gt;long-term credentials&lt;/em&gt;, such as AWS access keys and secret keys, X.509 certificates, SSH keys, passwords for web app logins, or MFA devices.&lt;/p&gt;

&lt;h3 id=&quot;groups&quot;&gt;Groups&lt;/h3&gt;

&lt;p&gt;An &lt;em&gt;IAM group&lt;/em&gt; is a set of IAM users. The policies attached to a group define the permissions shared by its members. Group is a convenience feature for efficiently managing permissions for a set of users. Therefore, although policies can be directly attached to a user, it is a better practice to attach policies to the groups that the user is a member of.&lt;/p&gt;

&lt;h3 id=&quot;roles&quot;&gt;Roles&lt;/h3&gt;

&lt;p&gt;An &lt;em&gt;IAM role&lt;/em&gt; defines a set of permissions via the attached policies. Roles cannot make direct requests to AWS services, they are meant to be assumed by &lt;em&gt;trusted entities&lt;/em&gt;, including IAM users and AWS services. The main use case of roles is to delegate accesses with defined permissions to those trusted entities, without having to share long-term credentials with them.&lt;/p&gt;

&lt;p&gt;The trust relationship is defined in the role’s trust policy when the role is created, as shown in the screenshot below, where the trusted entity can be either an AWS service, or a user (Another AWS account, Web identity, or SAML 2.0 federation). So a role is a container of polices, which define either permissions or trust relationships.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center&quot; src=&quot;https://user-images.githubusercontent.com/15970333/102698329-ce354280-423c-11eb-9022-5396d57641a1.png&quot; alt=&quot;screenshot IAM create role&quot; /&gt;&lt;/p&gt;

&lt;p&gt;One can even use roles to delegate access between AWS accounts. To assume a role from a different account, that account must be trusted by the role.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, policies can be attached to both roles and groups. Unfortunately, roles can not be attached to groups – that would be a nice feature. A big difference between groups and roles: groups are for users of the same account only, whereas roles can be assumed by users or services, from the same account or other accounts.&lt;/p&gt;

&lt;h2 id=&quot;instance-profiles&quot;&gt;Instance profiles&lt;/h2&gt;

&lt;p&gt;It might be arguable whether instance profile is an IAM core concept – it is even not visible in the AWS console&lt;sup id=&quot;fnref:4&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. Nevertheless, it is a topic that one shall be aware of when working with EC2, which is no doubt a core AWS service.&lt;/p&gt;

&lt;p&gt;An instance profile is a container for a single IAM role – it can be mapped to one and only one role. An instance can be launched with or without an instance profile. If it is launched with an instances profile, the corresponding role is associated with the instance, or &lt;em&gt;passed to&lt;/em&gt; the instance in AWS terms&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;. This association makes temporary credentials available to the applications on those instances. With the temporary credentials, the applications obtain the permissions defined in the role&lt;sup id=&quot;fnref:5&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;. Permissions are required, if the applications need to access other AWS services, which is a typical use case.&lt;/p&gt;

&lt;p&gt;Without instance profiles, the way of granting permissions to those applications is to deploy the access keys &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; on the EC2 instances. However, it is challenging to do so: the administrator would need to manage those keys, and the DevOps would need to make them available to the applications, e.g., by including them in the application configurations. The access keys are long term credentials owned by users, so groups and polices need to be set up properly. Key rotations and automatically launched instances make it even more complicated.&lt;/p&gt;

&lt;p&gt;So the major benefit of using instance profiles is the removal of long-term credentials from EC2 instances. The difference between roles and instance profiles: roles can be assumed by human users, whereas instance profiles can only be attached to instances at the launch time.&lt;/p&gt;

&lt;p&gt;However, I still have an open question for myself and the reader: when launching an instance, would it be easier to directly specify the required IAM role, instead of indirectly specifying the same but via an instance profile, which corresponds 1:1 to the role?&lt;/p&gt;

&lt;h2 id=&quot;how-they-play-together&quot;&gt;How they play together&lt;/h2&gt;

&lt;p&gt;To grant permissions to a human user, we shall first check if any &lt;a href=&quot;https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#aws-managed-policies&quot;&gt;AWS managed policy&lt;/a&gt; can be directly used. If not, a customer managed policy can be created. The policy can be attached to either a group which the user is a member of, or a role which the user can assume.&lt;/p&gt;

&lt;p&gt;Let’s assume a set of permissions (for accessing some sample AWS service or resource) are defined in a policy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt;, which has been attached to a group &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;g&lt;/code&gt; and a role &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;r&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For an IAM user to obtain permissions defined in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt;, there are two options:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;being a member of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;g&lt;/code&gt;, or&lt;/li&gt;
  &lt;li&gt;assuming &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;r&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For an instance to obtain the same set of permissions, there are also two options:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;launching it with an instance profile corresponding to role &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;r&lt;/code&gt;, or&lt;/li&gt;
  &lt;li&gt;deploying a pair of access and secret keys (created by a member of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;g&lt;/code&gt;) on it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img class=&quot;center&quot; src=&quot;https://user-images.githubusercontent.com/15970333/102716135-279f7f00-42da-11eb-946e-2a0022592493.png&quot; alt=&quot;How the IAM concepts play together&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;This post documents my understanding of IAM and some of its core concepts, their use cases, and their relationships. It cannot replace any AWS documentation – on the contrary – the official AWS documentation is the source of truth. However, the understanding from an AWS user’s perspective hopefully would be helpful for some other AWS users.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt; &lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;AWS has multiple policy types. In this post, we only discuss the &lt;a href=&quot;https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_id-based&quot;&gt;identity-based policies&lt;/a&gt;. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;The source of the example is &lt;a href=&quot;https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_ec2_tag-owner.html&quot;&gt;here&lt;/a&gt;. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;When an IAM role is created using the AWS console, an &lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html&quot;&gt;instance profile is created automatically behind the scenes by AWS&lt;/a&gt;. &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;The user launching the instance needs to have the &lt;a href=&quot;https://aws.amazon.com/blogs/security/granting-permission-to-launch-ec2-instances-with-iam-roles-passrole-permission/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RunInstances&lt;/code&gt; and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PassRole&lt;/code&gt; permissions&lt;/a&gt;. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;The applications running on that instance &lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#instance-metadata-security-credentials&quot;&gt;automatically get the (short-term) credentials from that instance’s metadata service&lt;/a&gt; and obtain the permissions defined in the role passed via the instance profile. &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Yi Ou</name></author><category term="post" /><category term="Cloud" /><category term="AWS" /><summary type="html">What are policies in IAM? What are the differences between groups and roles? What is an instance profile and when shall it be used? What are the differences between roles and instance profiles?</summary></entry><entry><title type="html">Null gotchas in Apache Pig</title><link href="https://ouyi.github.io/post/2018/09/01/null-gotchas-in-apache-pig.html" rel="alternate" type="text/html" title="Null gotchas in Apache Pig" /><published>2018-09-01T15:23:07+00:00</published><updated>2018-10-05T19:45:21+00:00</updated><id>https://ouyi.github.io/post/2018/09/01/null-gotchas-in-apache-pig</id><content type="html" xml:base="https://ouyi.github.io/post/2018/09/01/null-gotchas-in-apache-pig.html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#boolean-operators-and-filters&quot; id=&quot;markdown-toc-boolean-operators-and-filters&quot;&gt;Boolean operators and filters&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#regular-expressions&quot; id=&quot;markdown-toc-regular-expressions&quot;&gt;Regular expressions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#null-values-are-viral&quot; id=&quot;markdown-toc-null-values-are-viral&quot;&gt;Null values are viral&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#grouping-and-joining-nulls&quot; id=&quot;markdown-toc-grouping-and-joining-nulls&quot;&gt;Grouping and joining nulls&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#summary&quot; id=&quot;markdown-toc-summary&quot;&gt;Summary&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#references&quot; id=&quot;markdown-toc-references&quot;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apache Pig’s &lt;strong&gt;concept of null&lt;/strong&gt; was not straight forward to me, although it is clearly documented in the &lt;a href=&quot;#gates&quot;&gt;Pig book&lt;/a&gt; and highlighted in one of its early chapters:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;It is important to understand that in Pig the concept of null is the same as in SQL, which is completely different from the concept of null in C, Java, Python, etc. &lt;cite&gt;– &lt;a href=&quot;#gates&quot;&gt;Gates, A. and Dai, D., 2016&lt;/a&gt;, p. 26&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I missed to read this and had some fun :wink: investigating a production issue related to nulls. For people like me who do not read (tech) books front to back, it is easier to have all the pieces of information related to a topic together in one place. Therefore the following is a collection of important points on the concept of null from the book, together with some simple experiments.&lt;/p&gt;

&lt;h2 id=&quot;boolean-operators-and-filters&quot;&gt;Boolean operators and filters&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;For Boolean operators, nulls follow the SQL ternary logic. Thus, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x == null&lt;/code&gt; results in a value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; (even when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; &lt;em&gt;is also&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;) or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;. Filters pass through only those values that are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;. &lt;cite&gt;– &lt;a href=&quot;#gates&quot;&gt;Gates, A. and Dai, D., 2016&lt;/a&gt;, pp. 54-55&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Apache Pig, Boolean expressions with nulls can yield &lt;a href=&quot;https://en.wikipedia.org/wiki/Three-valued_logic&quot;&gt;three possible values&lt;/a&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;. This is not always obvious and &lt;strong&gt;can cause confusion&lt;/strong&gt;, because people would expect a Boolean operator to return either &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;, as it does in some other programming languages, e.g., Python or Java.&lt;/p&gt;

&lt;p&gt;When I first saw the following code:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-pig&quot;&gt;A = load 'input.txt' as (k:int, v:int);
B = filter A by k == 1;
C = filter A by k != 1;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I naively believed that the union of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C&lt;/code&gt; is equivalent to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt;, following the &lt;a href=&quot;https://en.wikipedia.org/wiki/Complement_(set_theory)#Properties&quot;&gt;Complement Laws&lt;/a&gt;, which says the union of a set $S$ and its absolute complement $S^\complement$ is the universe $U$, i.e.,&lt;/p&gt;

\[S \cup S^\complement = U.\]

&lt;p&gt;The problem is that in our example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C&lt;/code&gt; is not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt;’s absolute complement, due to the nulls, i.e.,&lt;/p&gt;

\[B^\complement = C \cup \text{\{records whose k field is null\}}\]

\[\Longrightarrow\]

\[A = B \cup B^\complement = B \cup C \cup \text{\{records whose k field is null\}}.\]

&lt;p&gt;So the Complement Laws still apply. We just need to keep in mind that the absolute complement of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter A by condition&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter A by not condition&lt;/code&gt; &lt;strong&gt;plus&lt;/strong&gt; the records whose k field &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is null&lt;/code&gt;. This can be demonstrated by the following experiments.&lt;/p&gt;

&lt;p&gt;First, lets prepare an input file, which contains a record with a null field:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cat &amp;gt; input1.txt
1,X
,Y
3,Z
CTRL+D
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, we can run the following Pig commands and examine the dumped values:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-pig&quot;&gt;A = load 'input1.txt' using PigStorage(',') as (k:int, v:chararray);

B = filter A by k == 1;
dump B;
-- (1,X)

C = filter A by k != 1;
dump C;
-- (3,Z)

D = filter A by k is null;
dump D;
-- (,Y)

U = union B, C, D;
dump U;
-- (1,X)
-- (,Y)
-- (3,Z)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note that the record &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(,Y)&lt;/code&gt; passes &lt;strong&gt;neither&lt;/strong&gt; the filter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k == 1&lt;/code&gt; &lt;strong&gt;nor&lt;/strong&gt; the filter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k != 1&lt;/code&gt;. It only qualifies for the filter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k is null&lt;/code&gt;. Therefore, only the union of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;D&lt;/code&gt; can be equivalent to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;regular-expressions&quot;&gt;Regular expressions&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Likewise, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; neither matches nor fails to match any regular expression value. &lt;cite&gt;– &lt;a href=&quot;#gates&quot;&gt;Gates, A. and Dai, D., 2016&lt;/a&gt;, pp. 54-55&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Again, lets first prepare an input file, which contains a record with a null field:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cat &amp;gt; input2.txt
a,X
,Y
c,Z
CTRL+D
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, we can run the following Pig commands and examine the dumped values:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-pig&quot;&gt;A = load 'input2.txt' using PigStorage(',') as (k:chararray, v:chararray);

B = filter A by k matches 'a';
dump B;
-- (a,X)

C = filter A by not (k matches 'a');
dump C;
-- (c,Z)

D = filter A by k is null;
dump D;
-- (,Y)

U = union B, C, D;
dump U;
-- (a,X)
-- (,Y)
-- (c,Z)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;null-values-are-viral&quot;&gt;Null values are viral&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Null values are viral for all arithmetic operators. That is, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x + null == null&lt;/code&gt; for all values of x.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pig also provides a binary condition operator, often referred to as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bincond&lt;/code&gt;. It begins with a Boolean test, followed by a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt;, then the value to return if the test is true, then a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:&lt;/code&gt;, and finally the value to return if the test is false. If the test returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;, bincond returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;. &lt;cite&gt;– &lt;a href=&quot;#gates&quot;&gt;Gates, A. and Dai, D., 2016&lt;/a&gt;, p. 48&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This can be verified with the following tests:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-pig&quot;&gt;A = load 'input1.txt' using PigStorage(',') as (k:int, v:chararray);
dump A;
-- (1,X)
-- (,Y)
-- (3,Z)

B = foreach A generate k + 1, v;
dump B;
-- (2,X)
-- (,Y)
-- (4,Z)

C = foreach A generate (k == null ? 2 : k), v;
-- (,X)
-- (,Y)
-- (,Z)

D = foreach A generate (k is null ? 2 : k), v;
-- (1,X)
-- (2,Y)
-- (3,Z)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note the different bincond (ternary operator) expressions used to generate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;D&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k == null&lt;/code&gt; always resulted in nulls, whereas &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k is null&lt;/code&gt; delivered the intended results (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;).&lt;/p&gt;

&lt;h2 id=&quot;grouping-and-joining-nulls&quot;&gt;Grouping and joining nulls&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Finally, group handles nulls in the same way that SQL handles them: by collecting all records with a null key into the same group. &lt;cite&gt;– &lt;a href=&quot;#gates&quot;&gt;Gates, A. and Dai, D., 2016&lt;/a&gt;, p. 44&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&quot;language-pig&quot;&gt;A = load 'input3.txt' using PigStorage(',') as (k:int, v:chararray);
dump A;
-- (1,X)
-- (,Y)
-- (3,Z)
-- (,G)

B = group A by k;
dump B;
-- (1,{(1,X)})
-- (3,{(3,Z)})
-- (,{(,G),(,Y)})
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
  &lt;p&gt;As in SQL, null values for keys do not match anything, even null values from the other input. So, for inner joins, all records with null key values are dropped. For outer joins, they will be retained but will not match any records from the other input. &lt;cite&gt;– &lt;a href=&quot;#gates&quot;&gt;Gates, A. and Dai, D., 2016&lt;/a&gt;, p. 47&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&quot;language-pig&quot;&gt;A = load 'input3.txt' using PigStorage(',') as (k:int, v:chararray);
B = load 'input3.txt' using PigStorage(',') as (k:int, v:chararray);

C = join A by k, B by k;
dump C;
-- (1,X,1,X)
-- (3,Z,3,Z)

D = join A by k full outer, B by k;
dump D;
-- (1,X,1,X)
-- (3,Z,3,Z)
-- (,G,,)
-- (,Y,,)
-- (,,,G)
-- (,,,Y)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note grouping treats nulls as identical values, whereas joining treats nulls as different (by not matching them).&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;In a nut shell, nulls in Apache Pig:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;can be produced by Boolean expressions&lt;/li&gt;
  &lt;li&gt;are viral for all arithmetic operators&lt;/li&gt;
  &lt;li&gt;yields nulls in equality checks (unless &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is null&lt;/code&gt; is used for the check)&lt;/li&gt;
  &lt;li&gt;yields nulls in regex match checks&lt;/li&gt;
  &lt;li&gt;are treated as identical values for grouping, and&lt;/li&gt;
  &lt;li&gt;are treated as different values for joining&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;p&gt;&lt;a name=&quot;gates&quot;&gt;&lt;/a&gt;Gates, A. and Dai, D., 2016. Programming pig: Dataflow scripting with hadoop. “O’Reilly Media, Inc.”.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/15970333/102699855-8a483a80-4248-11eb-89c7-e228e4dd30f4.gif&quot; alt=&quot;pig-logo&quot; title=&quot;Apache Pig logo&quot; /&gt;&lt;/p&gt;</content><author><name>Yi Ou</name></author><category term="post" /><category term="Big Data" /><summary type="html">Apache Pig’s concept of null was not straight forward to me, although it is clearly documented in the Pig book and highlighted in one of its early chapters:</summary></entry><entry xml:lang="zh"><title type="html">YARN的任务调度机制</title><link href="https://ouyi.github.io/post/2018/01/20/yarn-schedulers.html" rel="alternate" type="text/html" title="YARN的任务调度机制" /><published>2018-01-20T14:32:27+00:00</published><updated>2018-01-20T22:39:27+00:00</updated><id>https://ouyi.github.io/post/2018/01/20/yarn-schedulers</id><content type="html" xml:base="https://ouyi.github.io/post/2018/01/20/yarn-schedulers.html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#容量调度器&quot; id=&quot;markdown-toc-容量调度器&quot;&gt;容量调度器&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#层次化队列&quot; id=&quot;markdown-toc-层次化队列&quot;&gt;层次化队列&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#容量和弹性&quot; id=&quot;markdown-toc-容量和弹性&quot;&gt;容量和弹性&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#并发度控制&quot; id=&quot;markdown-toc-并发度控制&quot;&gt;并发度控制&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#运行时配置&quot; id=&quot;markdown-toc-运行时配置&quot;&gt;运行时配置&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#公平调度器&quot; id=&quot;markdown-toc-公平调度器&quot;&gt;公平调度器&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#调度策略&quot; id=&quot;markdown-toc-调度策略&quot;&gt;调度策略&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#权重和优先级&quot; id=&quot;markdown-toc-权重和优先级&quot;&gt;权重和优先级&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#并发度控制-1&quot; id=&quot;markdown-toc-并发度控制-1&quot;&gt;并发度控制&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#运行时配置-1&quot; id=&quot;markdown-toc-运行时配置-1&quot;&gt;运行时配置&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#相似和区别&quot; id=&quot;markdown-toc-相似和区别&quot;&gt;相似和区别&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#两者的相似性&quot; id=&quot;markdown-toc-两者的相似性&quot;&gt;两者的相似性&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#两者的区别&quot; id=&quot;markdown-toc-两者的区别&quot;&gt;两者的区别&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#总结&quot; id=&quot;markdown-toc-总结&quot;&gt;总结&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#参考文档&quot; id=&quot;markdown-toc-参考文档&quot;&gt;参考文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最近单位的Hadoop集群发生了死锁问题 (deadlock)，于是对Hadoop的调度程序发生了兴趣。YARN支持可插拔的任务调度器（严格来说也许应该叫资源分配器）。官方文档里面讲了两种：容量调度器 (capacity scheduler) 和公平调度器 (fair scheduler)。默认的是容量调度器。下面是啃完文档后的一点总结，浓缩的都是精华 :wink:。&lt;/p&gt;

&lt;h2 id=&quot;容量调度器&quot;&gt;容量调度器&lt;/h2&gt;

&lt;p&gt;容量调度器旨在允许多组织（或者多用户）共享大型集群，最大限度地提高集群的吞吐量和利用率，同时为每个组织提供容量保证。&lt;/p&gt;

&lt;h3 id=&quot;层次化队列&quot;&gt;层次化队列&lt;/h3&gt;

&lt;p&gt;容量调度器支持层次化队列结构，以确保在其他队列被允许使用闲置资源之前，在组织的子队列之间共享资源，从而提供更多的控制和可预测性。&lt;/p&gt;

&lt;p&gt;下面这个例子里，a，b，和c都是根队列的子队列，a和b还分别定义了自己的子队列：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;property&amp;gt;
  &amp;lt;name&amp;gt;yarn.scheduler.capacity.root.queues&amp;lt;/name&amp;gt;
  &amp;lt;value&amp;gt;a,b,c&amp;lt;/value&amp;gt;
  &amp;lt;description&amp;gt;The queues at the this level (root is the root queue).
  &amp;lt;/description&amp;gt;
&amp;lt;/property&amp;gt;

&amp;lt;property&amp;gt;
  &amp;lt;name&amp;gt;yarn.scheduler.capacity.root.a.queues&amp;lt;/name&amp;gt;
  &amp;lt;value&amp;gt;a1,a2&amp;lt;/value&amp;gt;
  &amp;lt;description&amp;gt;The queues at the this level (root is the root queue).
  &amp;lt;/description&amp;gt;
&amp;lt;/property&amp;gt;

&amp;lt;property&amp;gt;
  &amp;lt;name&amp;gt;yarn.scheduler.capacity.root.b.queues&amp;lt;/name&amp;gt;
  &amp;lt;value&amp;gt;b1,b2,b3&amp;lt;/value&amp;gt;
  &amp;lt;description&amp;gt;The queues at the this level (root is the root queue).
  &amp;lt;/description&amp;gt;
&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;容量和弹性&quot;&gt;容量和弹性&lt;/h3&gt;

&lt;p&gt;容量调度器同时兼顾容量保证和弹性。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;容量保证: 队列被分配一定量的可支配的资源。资源可以由集群容量的百分比来表示。&lt;/li&gt;
  &lt;li&gt;弹性：闲置资源可以分配给任何队列，也就是说队列可以使用超出其容量的资源。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这就确保资源以可预测的和弹性的方式提供给队列，从而防止集群中资源的人为孤岛，以提高利用率。&lt;/p&gt;

&lt;p&gt;两个相关的重要参数：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn.scheduler.capacity.&amp;lt;queue-path&amp;gt;.capacity&lt;/code&gt; 队列容量，用占集群总容量的百分比来表示。所有队列的容量合必须等于100。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn.scheduler.capacity.&amp;lt;queue-path&amp;gt;.maximum-capacity&lt;/code&gt; 队列最大容量，允许超过队列容量，以便能够使用其它队列的闲置资源。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;表达式&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;queue-path&amp;gt;&lt;/code&gt;是队列的路径，比如上面例子里面队列a1的路径为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root.a.a1&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;上面说到，队列可以使用超出其容量的资源。从运行容量低于保证容量的队列中再请求这些资源时，就要等目前使用这些资源的任务完成，或者不等它们完成就逐步地从资源使用量超过其保证容量的队列中强夺资源。资源的强夺以容器为单位。资源强夺英文叫preemption，是指不等待资源使用者释放资源而强行把资源抢夺过来。&lt;/p&gt;

&lt;p&gt;跟资源强夺相关的重要参数：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn.resourcemanager.scheduler.monitor.enable&lt;/code&gt; 要激活资源强夺，必须将此值设置为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn.resourcemanager.scheduler.monitor.policies&lt;/code&gt; 这里必须是一个跟调度器兼容的策略，默认的策略&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.apache.hadoop.yarn.server.resourcemanager.monitor.capacity.ProportionalCapacityPreemptionPolicy&lt;/code&gt;与容量调度器兼容。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn.resourcemanager.monitor.capacity.preemption.monitoring_interval&lt;/code&gt; 上述策略相邻两次调用之间等待的时间。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn.resourcemanager.monitor.capacity.preemption.max_wait_before_kill&lt;/code&gt; 发出强夺容器请求和强行终止容器之间的最长等待时间。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn.scheduler.capacity.&amp;lt;queue-path&amp;gt;.disable_preemption&lt;/code&gt; 可以将此配置设置为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;以选择性地禁用从给定队列进行资源强夺。这样可以实现一定程度的队列之间的优先级。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;值得一提的是，我从一些测试里观察到，资源强夺并未造成应用程序报错，可能是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_wait_before_kill&lt;/code&gt;相对比较长，也可能是Hadoop的容错机制起了作用。&lt;/p&gt;

&lt;h3 id=&quot;并发度控制&quot;&gt;并发度控制&lt;/h3&gt;

&lt;p&gt;并发度的控制可以通过下面两组参数来配置：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn.scheduler.capacity.maximum-applications / yarn.scheduler.capacity.&amp;lt;queue-path&amp;gt;.maximum-applications&lt;/code&gt;
应用程序的数量的最大值。这是一个硬性的限制，超过这个限制，提交任何申请时，都会被拒绝（应用程序会出错退出）。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn.scheduler.capacity.maximum-am-resource-percent / yarn.scheduler.capacity.&amp;lt;queue-path&amp;gt;.maximum-am-resource-percent&lt;/code&gt;
集群中可用于运行应用程序主控的资源的最大百分比。这个值间接控制并发活动应用程序的数量。这是一个柔性的限制。超过这个限制，提交的申请会被接受并在队列里面等候。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;运行时配置&quot;&gt;运行时配置&lt;/h3&gt;

&lt;p&gt;容量调度器支持运行时修改配置。管理员可以在集群运行时以安全的方式更改队列定义和属性（如容量，ACL），以最大限度地减少对用户的干扰。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;管理员可以在运行时添加额外的队列 ，但在运行时不能删除队列。&lt;/li&gt;
  &lt;li&gt;管理员可以在运行时停止队列，以确保当现有的应用程序运行完成时，不会提交新的应用程序。 如果队列处于“已停止”状态，则不能将新应用程序提交给它或它的任何子队列 。现有的应用程序将继续完成，因此队列可以正常排空。&lt;/li&gt;
  &lt;li&gt;管理员也可以启动已停止的队列。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;容量调度器的参数主要在capacity-scheduler.xml这个配置文件里。修改这个文件后，可以通过命令来更新队列配置。例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ vi $HADOOP_CONF_DIR/capacity-scheduler.xml
$ $HADOOP_YARN_HOME/bin/yarn rmadmin -refreshQueues
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;另外，一小部分容量调度器相关的参数在yarn-site.xml这个配置文件里。修改这里面的配置可能需要重启资源管理器才能使修改生效。&lt;/p&gt;

&lt;h2 id=&quot;公平调度器&quot;&gt;公平调度器&lt;/h2&gt;

&lt;p&gt;公平调度器允许YARN应用程序公平地共享大型集群中的资源。旨在让短应用程序在合理的时间内完成，而长应用程序也能得到资源。与容量调度器相似，它也利用分层的队列结构来进行资源分配，这里不再赘述。值得一提的是，公平调度器也允许为队列分配保证的最小份额。而且当队列不需要保证的份额时，多余的份额会在其他正在运行的应用程序之间进行分配，这一点也与容量调度器相似。&lt;/p&gt;

&lt;h3 id=&quot;调度策略&quot;&gt;调度策略&lt;/h3&gt;

&lt;p&gt;公平调度器支持可插拔的调度策略，并且支持为每个队列设置不同的调度策略。内置的调度策略有FIFO(FifoPolicy)，FAIR(FairSharePolicy)，和DRF(DominantResourceFairnessPolicy)。默认情况下，公平调度器只基于内存资源使用公平性调度策略 (FAIR)。它可以配置为主导资源公平策略 (DRF) 来调度内存和CPU。关于主导资源，我的理解就是瓶颈资源。&lt;/p&gt;

&lt;h3 id=&quot;权重和优先级&quot;&gt;权重和优先级&lt;/h3&gt;

&lt;p&gt;权重用来确定每个应用程序或队列应该获得的资源的比例。权重体现了应用程序或队列之间的优先级。&lt;/p&gt;

&lt;p&gt;两个权重相关的参数：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;应用权重：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn.scheduler.fair.sizebasedweight&lt;/code&gt; 是否根据应用程序的大小进行资源分配，而不是向所有应用程序提供相同多的资源而不考虑应用程序的大小。默认为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;队列权重：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;weight&lt;/code&gt; 队列可以与其他队列不成比例地共享集群。权重默认为1，而权重为2的队列应该接收的资源量约为默认权重的两倍。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;并发度控制-1&quot;&gt;并发度控制&lt;/h3&gt;

&lt;p&gt;公平调度器默认允许所有应用程序运行，但也可以通过配置文件限制每个用户和每个队列运行的应用程序的数量。限制应用程序不会导致任何随后提交的应用程序失败。提交后的应用程序只能在队列中等待，直到某些应用程序完成。这一点跟容器调度器的相关特性不太一样。个人觉得公平调度器的处理更加友好一点。&lt;/p&gt;

&lt;h3 id=&quot;运行时配置-1&quot;&gt;运行时配置&lt;/h3&gt;

&lt;p&gt;公平调度器也支持运行时修改配置。公平调度器相关的参数存在于两个配置文件里：yarn-site.xml和分配文件。修改yarn-site.xml里面的配置可能需要重启资源管理器才能使修改生效。而分配文件由&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn.scheduler.fair.allocation.file&lt;/code&gt;来指定，默认是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fair-scheduler.xml&lt;/code&gt;。分配文件每10秒重新加载一次，允许进行更改。&lt;/p&gt;

&lt;p&gt;公平调度器支持将正在运行的应用程序移动到不同的队列中。这对于将重要的应用程序移动到更高优先级的队列或将不重要的应用程序移动到更低优先级的队列可能会有用。例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;yarn application -movetoqueue appID -queue targetQueueName
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;相似和区别&quot;&gt;相似和区别&lt;/h2&gt;

&lt;h3 id=&quot;两者的相似性&quot;&gt;两者的相似性&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;都是Hadoop的可插拔调度程序。&lt;/li&gt;
  &lt;li&gt;都支持分层的队列结构。&lt;/li&gt;
  &lt;li&gt;都支持队列层面上的最少资源保证。&lt;/li&gt;
  &lt;li&gt;都支持闲置资源的分配和利用。&lt;/li&gt;
  &lt;li&gt;都提供了一套限制来防止单个应用程序，用户和队列独占整个队列或集群的资源。&lt;/li&gt;
  &lt;li&gt;都支持对队列分配策略 (应用程序到队列的映射，一般是基于用户名或组名的一些规则) 进行配置。&lt;/li&gt;
  &lt;li&gt;都可以通过URL&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&amp;lt;ResourceManager URL&amp;gt;/cluster/scheduler&lt;/code&gt;查看队列资源的分配和使用情况。&lt;/li&gt;
  &lt;li&gt;都支持限制每个用户和每个队列运行的应用程序的数量，以防止死锁。&lt;/li&gt;
  &lt;li&gt;都支持资源的强夺，以防止死锁。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;两者的区别&quot;&gt;两者的区别&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;对优先级的支持：容量调度器只是通过给队列分配不同的保证容量以及选择性地禁用资源强夺来部分支持优先级，而公平调度器可以通过应用程序和队列两个层面上的权重来实现优先级。&lt;/li&gt;
  &lt;li&gt;对并发应用数量的控制：容量调度器是硬性限制（也可以通过控制分配给应用程序主控的资源来间接实现柔性限制），而公平调度器直接是柔性限制。&lt;/li&gt;
  &lt;li&gt;队列内的资源分配策略：容量调度器是FIFO，而公平调度器支持三种内置的可插拔的策略：FIFO, FAIR, 和DRF。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;总体来说，容量调度器和公平调度器的相似多过区别。个人感觉，两者的区别主要是实现上的区别以及参数配置上的区别（运行时的性能和表现肯定也有区别）。公平调度器对优先级以及并发度的控制比容器调度器更直观。另外，本人猜测，&lt;a href=&quot;https://hortonworks.com/blog/yarn-capacity-scheduler/&quot;&gt;容器调度器由HortonWorks主导&lt;/a&gt;，而&lt;a href=&quot;https://blog.cloudera.com/blog/2016/01/untangling-apache-hadoop-yarn-part-3/&quot;&gt;公平调度器由cloudera主导&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;最后一点小提示：调度器可以用terasort这样的MapReduce程序来进行简单测试。比如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# 生成100GB的输入
hadoop jar /usr/lib/hadoop-mapreduce/hadoop-mapreduce-examples.jar teragen 1073741824 /tmp/teraInput

# 生成20个应用程序，并放入myqueue队列里
for i in $(seq -w 00 19); do
    nohup hadoop jar /usr/lib/hadoop-mapreduce/hadoop-mapreduce-examples.jar terasort -Dmapreduce.job.queuename=myqueue /tmp/teraInput /tmp/teraOutput-$i &amp;amp;&amp;gt; $i.log &amp;amp;
    sleep 1;
done
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;参考文档&quot;&gt;参考文档&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;容量调度器: &lt;a href=&quot;https://hadoop.apache.org/docs/r2.7.3/hadoop-yarn/hadoop-yarn-site/CapacityScheduler.html&quot;&gt;capacity scheduler&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;公平调度器: &lt;a href=&quot;https://hadoop.apache.org/docs/r2.7.3/hadoop-yarn/hadoop-yarn-site/FairScheduler.html&quot;&gt;fair scheduler&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/15970333/102699853-874d4a00-4248-11eb-8d50-302b5ebc7b57.jpg&quot; alt=&quot;hadoop-logo&quot; title=&quot;Apache Hadoop logo&quot; /&gt;&lt;/p&gt;</content><author><name>Yi Ou</name></author><category term="post" /><category term="Big Data" /><summary type="html">最近单位的Hadoop集群发生了死锁问题 (deadlock)，于是对Hadoop的调度程序发生了兴趣。YARN支持可插拔的任务调度器（严格来说也许应该叫资源分配器）。官方文档里面讲了两种：容量调度器 (capacity scheduler) 和公平调度器 (fair scheduler)。默认的是容量调度器。下面是啃完文档后的一点总结，浓缩的都是精华 :wink:。</summary></entry><entry><title type="html">How does the GitHub Pages CNAME file work</title><link href="https://ouyi.github.io/post/2018/01/14/github-pages-cname-file.html" rel="alternate" type="text/html" title="How does the GitHub Pages CNAME file work" /><published>2018-01-14T08:50:00+00:00</published><updated>2018-01-19T19:50:41+00:00</updated><id>https://ouyi.github.io/post/2018/01/14/github-pages-cname-file</id><content type="html" xml:base="https://ouyi.github.io/post/2018/01/14/github-pages-cname-file.html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#create-a-test-project-on-github&quot; id=&quot;markdown-toc-create-a-test-project-on-github&quot;&gt;Create a test project on GitHub&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#setup&quot; id=&quot;markdown-toc-setup&quot;&gt;Setup&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#set-up-dns&quot; id=&quot;markdown-toc-set-up-dns&quot;&gt;Set up DNS&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#set-up-github-pages-redirect&quot; id=&quot;markdown-toc-set-up-github-pages-redirect&quot;&gt;Set up GitHub Pages redirect&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tests&quot; id=&quot;markdown-toc-tests&quot;&gt;Tests&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#requests-using-the-custom-domain-name&quot; id=&quot;markdown-toc-requests-using-the-custom-domain-name&quot;&gt;Requests using the custom domain name&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#requests-using-the-root-domain-name&quot; id=&quot;markdown-toc-requests-using-the-root-domain-name&quot;&gt;Requests using the root domain name&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#requests-using-the-default-dns-name&quot; id=&quot;markdown-toc-requests-using-the-default-dns-name&quot;&gt;Requests using the default DNS name&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GitHub Pages support custom domains, i.e., sites hosted there can be accessed
via DNS names like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.github.com&lt;/code&gt;, in addition to the default DNS names like
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ouyi.github.io&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ouyi.github.io/test&lt;/code&gt;. I did some tests to figure out how
it works. Those tests are described below. To repeat them&lt;sup id=&quot;fnref:dns_cache&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:dns_cache&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, you need a
GitHub account and a test domain.&lt;/p&gt;

&lt;h2 id=&quot;create-a-test-project-on-github&quot;&gt;Create a test project on GitHub&lt;/h2&gt;

&lt;p&gt;First, lets create a test project on GitHub. This is the test project I
created: &lt;a href=&quot;https://ouyi.github.io/test&quot;&gt;ouyi.github.io/test&lt;/a&gt;. Based on this test
project, we can create a minimal working example for GitHub Pages. That
requires only a &lt;a href=&quot;https://github.com/ouyi/test/blob/gh-pages/index.html&quot;&gt;minimalistic HTML index page on the special branch
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt;&lt;/a&gt;. With that
in place, we have created a so called &lt;a href=&quot;https://help.github.com/articles/user-organization-and-project-pages/&quot;&gt;Project Pages
site&lt;/a&gt;,
which is accessible at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://ouyi.github.io/test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we can test it with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; command-line tool&lt;sup id=&quot;fnref:browsers&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:browsers&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -iL &quot;http://ouyi.github.io/test&quot;
HTTP/1.1 301 Moved Permanently
Server: GitHub.com
Content-Type: text/html
Location: https://ouyi.github.io/test
X-GitHub-Request-Id: C902:209A2:BA02A6E:1012C7CB:5A5B61BF
Content-Length: 178
Accept-Ranges: bytes
Date: Sun, 14 Jan 2018 14:02:13 GMT
Via: 1.1 varnish
Age: 294
Connection: keep-alive
X-Served-By: cache-hhn1522-HHN
X-Cache: HIT
X-Cache-Hits: 1
X-Timer: S1515938534.844165,VS0,VE0
Vary: Accept-Encoding
X-Fastly-Request-ID: 9bde18fb5f0118cbb043b30b9aae1850eed3796f

HTTP/1.1 301 Moved Permanently
Server: GitHub.com
Content-Type: text/html
Location: https://ouyi.github.io/test/
X-GitHub-Request-Id: C8FC:4765:17E5632:21B888F:5A5B61BF
Content-Length: 178
Accept-Ranges: bytes
Date: Sun, 14 Jan 2018 14:02:13 GMT
Via: 1.1 varnish
Age: 294
Connection: keep-alive
X-Served-By: cache-hhn1545-HHN
X-Cache: HIT
X-Cache-Hits: 2
X-Timer: S1515938534.941957,VS0,VE0
Vary: Accept-Encoding
X-Fastly-Request-ID: 93fba68c26ab60ad62d2ac5a6c584b787532adcf

HTTP/1.1 200 OK
Server: GitHub.com
Content-Type: text/html; charset=utf-8
Last-Modified: Sun, 14 Jan 2018 13:56:49 GMT
Access-Control-Allow-Origin: *
Expires: Sun, 14 Jan 2018 14:07:20 GMT
Cache-Control: max-age=600
X-GitHub-Request-Id: 6990:209A3:5583E4F:760347E:5A5B61BE
Content-Length: 79
Accept-Ranges: bytes
Date: Sun, 14 Jan 2018 14:02:13 GMT
Via: 1.1 varnish
Age: 294
Connection: keep-alive
X-Served-By: cache-hhn1545-HHN
X-Cache: HIT
X-Cache-Hits: 2
X-Timer: S1515938534.955052,VS0,VE0
Vary: Accept-Encoding
X-Fastly-Request-ID: 59d0f0fc8487678fe5e489f73b67f96c4de5afc8

&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
    Hello, World!
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Quite a lot was going on to serve our minimal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hello, World!&lt;/code&gt; page. The first
HTTP response redirects the user agent (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; or a browser) to the HTTPS
version of the same location. The second response redirects to the canonical
location (which has a trailing slash &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;) of the site. Finally, the third
response returns the HTML page.&lt;/p&gt;

&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;

&lt;h3 id=&quot;set-up-dns&quot;&gt;Set up DNS&lt;/h3&gt;

&lt;p&gt;Now we will set up DNS to point our test domain names to the &lt;a href=&quot;https://help.github.com/articles/setting-up-an-apex-domain/&quot;&gt;GitHub Pages IP
addresses&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We follow the convention to &lt;a href=&quot;https://blog.cloudflare.com/zone-apex-naked-domain-root-domain-cname-supp/&quot;&gt;create an &lt;em&gt;A record&lt;/em&gt; for the apex
domain&lt;/a&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;builders-it.de&lt;/code&gt; to be able to resolve it to an IP address (we picked
192.30.252.154), and a &lt;em&gt;CNAME record&lt;/em&gt; for the www subdomain
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.builders-it.de&lt;/code&gt; as an alias of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;builders-it.de&lt;/code&gt;. How to create those
records depends on the DNS provider. I use 1und1.de, which seems to only allow
one A record&lt;sup id=&quot;fnref:a_records&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:a_records&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; for the apex domain (a.k.a. the root domain). But that is
sufficient for our tests.&lt;/p&gt;

&lt;p&gt;We can verify the DNS set up with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dig&lt;/code&gt; command-line tool&lt;sup id=&quot;fnref:host_cmd&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:host_cmd&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;. After some
minutes of waiting, the output shall look like the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ dig www.builders-it.de +nostats +nocomments

; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.8.3-P1 &amp;lt;&amp;lt;&amp;gt;&amp;gt; www.builders-it.de +nostats +nocomments
;; global options: +cmd
;www.builders-it.de.		IN	A
www.builders-it.de.	3413	IN	CNAME	builders-it.de.
builders-it.de.		3413	IN	A	192.30.252.154
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;set-up-github-pages-redirect&quot;&gt;Set up GitHub Pages redirect&lt;/h3&gt;

&lt;p&gt;Now we need to tell GitHub Pages our domain name. That can be done by creating
a &lt;a href=&quot;https://github.com/ouyi/test/blob/gh-pages/CNAME&quot;&gt;CNAME file containing the www
subdomain&lt;/a&gt;, again, on the
special branch &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt;. It could also be done using the GitHub Web UI, as
shown in the following screen shot:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/15970333/34918207-7d8db288-f94f-11e7-8490-f6014c0567fc.png&quot; alt=&quot;screen shot 2018-01-14 at 14 56 30&quot; title=&quot;GitHub Pages custom domain setting under project settings&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;tests&quot;&gt;Tests&lt;/h2&gt;

&lt;h3 id=&quot;requests-using-the-custom-domain-name&quot;&gt;Requests using the custom domain name&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -iL &quot;http://www.builders-it.de&quot;
HTTP/1.1 200 OK
Server: GitHub.com
Date: Sun, 14 Jan 2018 09:58:16 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 79
Vary: Accept-Encoding
Last-Modified: Sun, 14 Jan 2018 08:38:36 GMT
Vary: Accept-Encoding
Access-Control-Allow-Origin: *
Expires: Sun, 14 Jan 2018 10:08:16 GMT
Cache-Control: max-age=600
Accept-Ranges: bytes
X-GitHub-Request-Id: FB7C:7582:388994F:4EBCAA3:5A5B29B8

&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
    Hello, World!
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Cool, it works! The following command shows the requests sent to the server.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -iLv &quot;http://www.builders-it.de&quot; 2&amp;gt;&amp;amp;1 | grep '^&amp;gt; '
&amp;gt; GET / HTTP/1.1
&amp;gt; Host: www.builders-it.de
&amp;gt; User-Agent: curl/7.54.0
&amp;gt; Accept: */*
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The GitHub Pages IP address (which is most likely a load balancer IP address)
in this example is resolved in two steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;DNS figures out the canonical domain name (which is the root domain name in
our setup) for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.builders-it.de&lt;/code&gt; using the CNAME record, i.e.,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.builders-it.de =&amp;gt; builders-it.de&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;DNS figures out the IP address for the canonical domain name using the A
record, i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;builders-it.de =&amp;gt; 192.30.252.154&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The CNAME could also be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.builders-it.de =&amp;gt; ouyi.github.io&lt;/code&gt;, as documented
in the GitHub official documentation. In that case, the GitHub name servers are
responsible for resolving the IP address. The above two steps would be:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;DNS figures out the canonical domain name (which is the root domain name in
our setup) for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.builders-it.de&lt;/code&gt; using the CNAME record, i.e.,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.builders-it.de =&amp;gt; ouyi.github.io&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;GitHub Pages name servers figure out an IP address for the GitHub Pages
subdomain, i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ouyi.github.io =&amp;gt; some ip address&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;requests-using-the-root-domain-name&quot;&gt;Requests using the root domain name&lt;/h3&gt;

&lt;p&gt;Accessing the site using the root domain name involves a 301 redirect to the
www subdomain.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -iL &quot;http://builders-it.de&quot;
HTTP/1.1 301 Moved Permanently
Server: GitHub.com
Date: Sun, 14 Jan 2018 10:29:20 GMT
Content-Type: text/html
Content-Length: 178
Location: http://www.builders-it.de/
X-GitHub-Request-Id: CB47:7585:74DABB1:A2D8546:5A5B3100

HTTP/1.1 200 OK
Server: GitHub.com
Date: Sun, 14 Jan 2018 10:29:20 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 79
Vary: Accept-Encoding
Last-Modified: Sun, 14 Jan 2018 08:38:36 GMT
Vary: Accept-Encoding
Access-Control-Allow-Origin: *
Expires: Sun, 14 Jan 2018 10:39:20 GMT
Cache-Control: max-age=600
Accept-Ranges: bytes
X-GitHub-Request-Id: CB22:7582:389741B:4ED0158:5A5B3100

&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
    Hello, World!
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can see there were two HTTP requests:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -iLv &quot;http://builders-it.de&quot; 2&amp;gt;&amp;amp;1 | grep '^&amp;gt; '
&amp;gt; GET / HTTP/1.1
&amp;gt; Host: builders-it.de
&amp;gt; User-Agent: curl/7.54.0
&amp;gt; Accept: */*
&amp;gt;
&amp;gt; GET / HTTP/1.1
&amp;gt; Host: www.builders-it.de
&amp;gt; User-Agent: curl/7.54.0
&amp;gt; Accept: */*
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first request goes directly to the GitHub Pages IP address (due to the A
record). The GitHub server at that address returns a 301 response, which
redirects the user agent to the www subdomain (because of the CNAME file). This
kind of redirect seems to be undocumented by GitHub Pages.&lt;/p&gt;

&lt;h3 id=&quot;requests-using-the-default-dns-name&quot;&gt;Requests using the default DNS name&lt;/h3&gt;

&lt;p&gt;To see whether the default DNS name for the Project Pages site is still
working, we can repeat the very first test we did after we &lt;a href=&quot;#create-a-test-project-on-github&quot;&gt;created the test
project on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -iL &quot;http://ouyi.github.io/test&quot;
HTTP/1.1 301 Moved Permanently
Server: GitHub.com
Content-Type: text/html
Location: http://www.builders-it.de
X-GitHub-Request-Id: 6402:209A2:B8CB7A7:FF7EA72:5A5B2189
Content-Length: 178
Accept-Ranges: bytes
Date: Sun, 14 Jan 2018 09:47:55 GMT
Via: 1.1 varnish
Age: 1473
Connection: keep-alive
X-Served-By: cache-hhn1544-HHN
X-Cache: HIT
X-Cache-Hits: 1
X-Timer: S1515923275.411885,VS0,VE0
Vary: Accept-Encoding
X-Fastly-Request-ID: dbefaf4a7707a8401e813836d56100c55a5ab412

HTTP/1.1 200 OK
Server: GitHub.com
Date: Sun, 14 Jan 2018 09:47:55 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 79
Vary: Accept-Encoding
Last-Modified: Sun, 14 Jan 2018 08:38:36 GMT
Vary: Accept-Encoding
Access-Control-Allow-Origin: *
Expires: Sun, 14 Jan 2018 09:57:49 GMT
Cache-Control: max-age=600
Accept-Ranges: bytes
X-GitHub-Request-Id: FBCA:7582:38852CC:4EB67B9:5A5B274B

&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
    Hello, World!
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It works, but differently. There is no redirect to the HTTPS location and there
is no redirect to add the trailing slash. The first response redirects the user
agent to the www subdomain (due to the CNAME file).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -iLv &quot;http://ouyi.github.io/test&quot; 2&amp;gt;&amp;amp;1 | grep '^&amp;gt; '
&amp;gt; GET /test HTTP/1.1
&amp;gt; Host: ouyi.github.io
&amp;gt; User-Agent: curl/7.54.0
&amp;gt; Accept: */*
&amp;gt;
&amp;gt; Host: www.builders-it.de
&amp;gt; User-Agent: curl/7.54.0
&amp;gt; Accept: */*
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Interestingly, the redirect to add the trailing slash still occurs when
requesting the HTTPS URL directly, and there is no redirect to the custom
domain.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -iL &quot;https://ouyi.github.io/test&quot;
HTTP/1.1 301 Moved Permanently
Server: GitHub.com
Content-Type: text/html
Location: https://ouyi.github.io/test/
X-GitHub-Request-Id: 6CE4:4767:42B367D:5DF0107:5A5BABD8
Content-Length: 178
Accept-Ranges: bytes
Date: Sun, 14 Jan 2018 19:13:28 GMT
Via: 1.1 varnish
Age: 0
Connection: keep-alive
X-Served-By: cache-hhn1541-HHN
X-Cache: MISS
X-Cache-Hits: 0
X-Timer: S1515957208.141728,VS0,VE96
Vary: Accept-Encoding
X-Fastly-Request-ID: 0853013cea20df0ef053f4f85346032fffe9acea

HTTP/1.1 200 OK
Server: GitHub.com
Content-Type: text/html; charset=utf-8
Last-Modified: Sun, 14 Jan 2018 13:56:49 GMT
Access-Control-Allow-Origin: *
Expires: Sun, 14 Jan 2018 14:07:20 GMT
Cache-Control: max-age=600
X-GitHub-Request-Id: 6990:209A3:5583E4F:760347E:5A5B61BE
Content-Length: 79
Accept-Ranges: bytes
Date: Sun, 14 Jan 2018 19:13:28 GMT
Via: 1.1 varnish
Age: 0
Connection: keep-alive
X-Served-By: cache-hhn1541-HHN
X-Cache: HIT
X-Cache-Hits: 1
X-Timer: S1515957208.249646,VS0,VE134
Vary: Accept-Encoding
X-Fastly-Request-ID: b4f06f7769fd14f5924d09fe3729439816761733

&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
    Hello, World!
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Settng up custom domain for GitHub Pages involves two aspects: 1. Set up the
domain names to point to the GitHub Pages IP address(es) 2. Add a CNAME file in
the project to redirect HTTP requests to the custom domain.&lt;/p&gt;

&lt;p&gt;It seems that the DNS name in the CNAME file is used for the HTTP 301 redirect.
So, probably a better name for that file could be REDIRECT. Most of the DNS
providers nowadays also support HTTP redirects, which can be used, e.g., to
redirect HTTP requests to HTTPS locations. But they work the same way as the
GitHub redirect does, i.e., they use a Web server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:dns_cache&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;While doing DNS-related tests, it might be helpful to flush the host DNS cache from time to time, which can be done with various methods, e.g., you can clear host DNS cache with Chrome by opening the URL &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome://net-internals/#dns&lt;/code&gt; and click the corresponding button on that page, or if you are on a macbook with macOS Sierra, the host DNS cache can be cleared by the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo killall -HUP mDNSResponder&lt;/code&gt;. &lt;a href=&quot;#fnref:dns_cache&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:browsers&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Tests can also be done with browsers, e.g., Chrome developer tools. &lt;a href=&quot;#fnref:browsers&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:a_records&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Some DNS providers support multiple A records for the apex domain. &lt;a href=&quot;#fnref:a_records&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:host_cmd&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Similar information can also be obtained using the commands &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;host -a www.builders-it.de&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;host -a builders-it.de&lt;/code&gt;. &lt;a href=&quot;#fnref:host_cmd&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Yi Ou</name></author><category term="post" /><category term="Blogging" /><category term="DNS" /><summary type="html">GitHub Pages support custom domains, i.e., sites hosted there can be accessed via DNS names like www.github.com, in addition to the default DNS names like ouyi.github.io or ouyi.github.io/test. I did some tests to figure out how it works. Those tests are described below. To repeat them1, you need a GitHub account and a test domain. While doing DNS-related tests, it might be helpful to flush the host DNS cache from time to time, which can be done with various methods, e.g., you can clear host DNS cache with Chrome by opening the URL chrome://net-internals/#dns and click the corresponding button on that page, or if you are on a macbook with macOS Sierra, the host DNS cache can be cleared by the command sudo killall -HUP mDNSResponder. &amp;#8617;</summary></entry><entry><title type="html">Rendering table of contents in Jekyll</title><link href="https://ouyi.github.io/post/2017/12/31/jekyll-table-of-contents.html" rel="alternate" type="text/html" title="Rendering table of contents in Jekyll" /><published>2017-12-31T13:20:00+00:00</published><updated>2018-01-14T19:55:44+00:00</updated><id>https://ouyi.github.io/post/2017/12/31/jekyll-table-of-contents</id><content type="html" xml:base="https://ouyi.github.io/post/2017/12/31/jekyll-table-of-contents.html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#generating-the-toc&quot; id=&quot;markdown-toc-generating-the-toc&quot;&gt;Generating the TOC&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#adding-the-toc-heading&quot; id=&quot;markdown-toc-adding-the-toc-heading&quot;&gt;Adding the TOC heading&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#with-markdown&quot; id=&quot;markdown-toc-with-markdown&quot;&gt;With Markdown&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#with-javascript&quot; id=&quot;markdown-toc-with-javascript&quot;&gt;With JavaScript&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#with-css&quot; id=&quot;markdown-toc-with-css&quot;&gt;With CSS&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having a table of contents (TOC) makes an article more readable. Our goal is to
automatically generate TOC out of the headings in an article, for a site based
on Jekyll and hosted on GitHub Pages, which is the setup of this site.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/15970333/102717392-56215800-42e2-11eb-87c1-ca4df56966aa.jpg&quot; alt=&quot;avonlea-jewelry-2TmFSvM8d0Q-unsplash&quot; title=&quot;Table of content -- photo by Avonlea Jewelry on Unsplash&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;generating-the-toc&quot;&gt;Generating the TOC&lt;/h2&gt;

&lt;p&gt;The site generated by Jekyll is basically a set of static HTML pages. It is
therefore possible to collect the HTML heading elements (h1, h2, etc.) and
generate a TOC using JavaScript, as &lt;a href=&quot;https://github.com/gajus/contents#table-of-contents-toc-generator-similar-libraries&quot;&gt;these libraries
do&lt;/a&gt;.
However, those libraries do not work well, if the heading elements are not only
used in the article, but also used in the header or footer of the site, which
is the case for this site.  For example, the “Memory Spills” site title in the
footer is wrapped in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; tag by Jekyll.  That means “Memory Spills” would
appear in the TOC, which is not nice.&lt;/p&gt;

&lt;p&gt;Fortunately, rendering of table of contents (TOC) is supported by kramdown,
which is Jekyll 3.0.0’s default Markdown processor, and, as a side note, &lt;a href=&quot;https://help.github.com/articles/updating-your-markdown-processor-to-kramdown/&quot;&gt;the
only supported Markdown processor on GitHub
Pages&lt;/a&gt;.
Adding the following code snippet directly after the front matter will do the
trick:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;My front matter&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; TOC
{:toc}
My first paragraph.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that no empty lines shall be present between the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{:toc}&lt;/code&gt; tag and the
first paragraph of the article (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;My first paragraph.&lt;/code&gt; in the code example), or
else the latter will not be included in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post.excerpt&lt;/code&gt; variable, which is
used for multiple purposes on this site. First, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post.excerpt&lt;/code&gt; variable is used in &lt;a href=&quot;/post/2017/12/23/jekyll-customization.html#pagination-of-the-home-page&quot;&gt;the
home layout to show an excerpt of the post content for each post&lt;/a&gt;, in addition to its post title. That way the
post content can be previewed on the home page(s), before a reader decides to
click the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read more&lt;/code&gt; button which links to the complete post content. Second,
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post.excerpt&lt;/code&gt; variable is also used to populate the description meta
tags important for SEO.&lt;/p&gt;

&lt;h2 id=&quot;adding-the-toc-heading&quot;&gt;Adding the TOC heading&lt;/h2&gt;

&lt;p&gt;The rendered TOC is just a list of article headings. It would be nice if it has
the TOC heading “Contents” or “Table of contents”.  It turns out this is a bit
tricky.&lt;/p&gt;

&lt;h3 id=&quot;with-markdown&quot;&gt;With Markdown&lt;/h3&gt;

&lt;p&gt;To insert such a TOC heading, one can use the following markdown code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;My front matter&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;

&lt;span class=&quot;gs&quot;&gt;**Contents**&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; TOC
{:toc}
My first paragraph.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will insert a bold “Contents” line as the heading before the TOC, which is
nice, but the word “Contents” becomes a part of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post.excerpt&lt;/code&gt; variable,
and is consequently shown as a part of the preview, which is not so nice.
Therefore, I had to cut off the extra &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Contents&lt;/code&gt; string as follows:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-liquid&quot; data-lang=&quot;liquid&quot;&gt;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;excerpt&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip_html&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace_first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Contents'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lstrip&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;truncatewords&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;&amp;lt;a href=&quot;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;relative_url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;&quot;&amp;gt;&amp;amp;hellip; read more &amp;amp;raquo;&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Remember the ‘post.excerpt’ variable is also used to populate the description
meta tags. There I found no easy way to get rid of the extra &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Contents&lt;/code&gt; string,
which is not nice.&lt;/p&gt;

&lt;h3 id=&quot;with-javascript&quot;&gt;With JavaScript&lt;/h3&gt;

&lt;p&gt;Therefore, I removed the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;**Contents**&lt;/code&gt; heading from the
markdown, and came up with a JavaScript hack.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;domReady&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;markdown-toc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;toc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;insertAdjacentHTML&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;beforebegin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Table of contents&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Mozilla, Opera, Webkit&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;DOMContentLoaded&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;DOMContentLoaded&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callee&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;domReady&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attachEvent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// If IE event model is used&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ensure firing before onload&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attachEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;onreadystatechange&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readyState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;detachEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;onreadystatechange&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callee&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;domReady&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/ouyi/ouyi.github.io/blob/master/_includes/javascript.html&quot;&gt;The above
code&lt;/a&gt;
can be included (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ % include javascript.html %}&lt;/code&gt;) as the last line in
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_includes/footer.html&lt;/code&gt;, so that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag appears immediately before
the closing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;This works well without poluting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post.excerpt&lt;/code&gt; variable. But is it
possible to do this without the JavaScript hack?&lt;/p&gt;

&lt;h3 id=&quot;with-css&quot;&gt;With CSS&lt;/h3&gt;

&lt;p&gt;It turns out arbitrary text can be inserted using CSS, e.g.,&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-scss&quot; data-lang=&quot;scss&quot;&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;my-id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my text before the element with the id my-id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The complete solution is adding the following CSS code to &lt;a href=&quot;https://github.com/ouyi/ouyi.github.io/blob/master/_sass/custom.scss&quot;&gt;this file&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-scss&quot; data-lang=&quot;scss&quot;&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;markdown-toc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Contents&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;margin-left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$spacing-unit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;line-height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;@include&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;relative-font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;.25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;@include&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;media-query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$on-laptop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;@include&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;relative-font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;.125&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This pure CSS solution touches only one file, and, as with
the JavaScript solution, it does not polute the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post.excerpt&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;However, I am not sure how to deal with i18n, e.g., for an article written in
Chinese, one would prefer to add “目录” instead of “Contents”. That could be
easier with the JavaScript solution. If you have a good solution, please leave
a comment. Thanks!&lt;/p&gt;</content><author><name>Yi Ou</name></author><category term="post" /><category term="Jekyll" /><category term="JavaScript" /><category term="CSS" /><summary type="html">Having a table of contents (TOC) makes an article more readable. Our goal is to automatically generate TOC out of the headings in an article, for a site based on Jekyll and hosted on GitHub Pages, which is the setup of this site.</summary></entry><entry><title type="html">Customizing Jekyll theme</title><link href="https://ouyi.github.io/post/2017/12/23/jekyll-customization.html" rel="alternate" type="text/html" title="Customizing Jekyll theme" /><published>2017-12-23T07:56:11+00:00</published><updated>2017-12-31T14:05:16+00:00</updated><id>https://ouyi.github.io/post/2017/12/23/jekyll-customization</id><content type="html" xml:base="https://ouyi.github.io/post/2017/12/23/jekyll-customization.html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#override-theme-defaults&quot; id=&quot;markdown-toc-override-theme-defaults&quot;&gt;Override theme defaults&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#pagination-of-the-home-page&quot; id=&quot;markdown-toc-pagination-of-the-home-page&quot;&gt;Pagination of the home page&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#links-to-the-previous-and-next-pages&quot; id=&quot;markdown-toc-links-to-the-previous-and-next-pages&quot;&gt;Links to the previous and next pages&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#categories-and-tags&quot; id=&quot;markdown-toc-categories-and-tags&quot;&gt;Categories and tags&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#summary&quot; id=&quot;markdown-toc-summary&quot;&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Jekyll supports a lot of themes, which work quite well out of the box.  A theme
is a pre-defined set of styles, templates, and template variables. My site is
based on the default Jekyll theme: &lt;a href=&quot;https://github.com/jekyll/minima&quot;&gt;minima&lt;/a&gt;.
I had to do some customizations to make it work as I wish. Luckily, Jekyll
allows customizations of almost everything of the theme. This post shows some
of the customizations this site applies on top of minima.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/15970333/102717561-7ac9ff80-42e3-11eb-9ebc-f0b651688364.jpg&quot; alt=&quot;charlie-larkman-oi3Dwxyc0vE-unsplash&quot; title=&quot;Modified car -- photo by Charlie Larkman on Unsplash&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;override-theme-defaults&quot;&gt;Override theme defaults&lt;/h2&gt;

&lt;p&gt;The command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle show minima&lt;/code&gt; can be used to find the location where the theme
artifacts are installed, e.g.:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[ouyi.github.io]$ tree $(bundle show minima)
/home/ouyi/ouyi.github.io/.bundle/gems/minima-2.1.1
|-- LICENSE.txt
|-- README.md
|-- _includes
|   |-- disqus_comments.html
|   |-- footer.html
|   |-- google-analytics.html
|   |-- head.html
|   |-- header.html
|   |-- icon-github.html
|   |-- icon-github.svg
|   |-- icon-twitter.html
|   `-- icon-twitter.svg
|-- _layouts
|   |-- default.html
|   |-- home.html
|   |-- page.html
|   `-- post.html
|-- _sass
|   |-- minima
|   |   |-- _base.scss
|   |   |-- _layout.scss
|   |   `-- _syntax-highlighting.scss
|   `-- minima.scss
`-- assets
    `-- main.scss

5 directories, 20 files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To override the theme defaults, simply copy the related file from the theme
installation location to your project, under the same folder. For example, the
following customization adds feed link in the header:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[ouyi.github.io]$ diff .bundle/gems/minima-2.1.1/_includes/header.html _includes/header.html
27a28
&amp;gt;           &amp;lt;a class=&quot;page-link&quot; href=&quot;/feed.xml&quot;&amp;gt;&amp;lt;i class=&quot;fa fa-rss&quot; aria-hidden=&quot;true&quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As another example, to include custom styles, make a copy of the main.scss file
and add lines to import from any custom style sheets.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[ouyi.github.io]$ diff .bundle/gems/minima-2.1.1/assets/main.scss assets/main.scss
5a6
&amp;gt; @import &quot;custom&quot;;
[ouyi.github.io]$ ls _sass/
custom.scss
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By default, bundler installs gems in a central location shared by all ruby
projects. To change that, set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BUNDLE_DISABLE_SHARED_GEMS&lt;/code&gt; to true in the
bundler config file, e.g.:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cat .bundle/config
---
BUNDLE_DISABLE_SHARED_GEMS: &quot;true&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;pagination-of-the-home-page&quot;&gt;Pagination of the home page&lt;/h2&gt;

&lt;p&gt;By default, the minima’s home page shows the complete list of posts, which is
not nice. What I would prefer are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;split the home page into multiple pages, if that list become long&lt;/li&gt;
  &lt;li&gt;control the number of posts displayed per page&lt;/li&gt;
  &lt;li&gt;link from each page to the previous and next pages&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It turns out that Jekyll already has some support for
&lt;a href=&quot;https://jekyllrb.com/docs/pagination/&quot;&gt;pagination&lt;/a&gt;. To enable it, one has to
add a line to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;&lt;!--_--&gt; file, specifying the number of items per page,
e.g.: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paginator: 8&lt;/code&gt;. With pagination enabled, Jekyll populates a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paginator&lt;/code&gt;
liquid object.&lt;/p&gt;

&lt;p&gt;The following diff shows my changes to the default home layout, demonstrating
the use of the paginator object:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-liquid&quot; data-lang=&quot;liquid&quot;&gt;[ouyi.github.io]$ diff .bundle/gems/minima-2.1.1/_layouts/home.html _layouts/home.html
7,8c7,8
&amp;lt;   &amp;lt;h1 class=&quot;page-heading&quot;&amp;gt;Posts&amp;lt;/h1&amp;gt;
&amp;lt;
---
&amp;gt;   &amp;lt;h1 class=&quot;post-title&quot;&amp;gt;Posts&amp;lt;/h1&amp;gt;
&amp;gt;
12c12
&amp;lt;     &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;site.posts&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
---
&amp;gt;     &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;paginator.posts&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
19a20
&amp;gt;         &lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;excerpt&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip_html&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace_first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Contents'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lstrip&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;truncatewords&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;&amp;lt;a href=&quot;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;relative_url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;&quot;&amp;gt;&amp;amp;hellip; read more &amp;amp;raquo;&amp;lt;/a&amp;gt;
24,25c25
&amp;lt;   &amp;lt;p class=&quot;rss-subscribe&quot;&amp;gt;subscribe &amp;lt;a href=&quot;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/feed.xml&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;relative_url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;&quot;&amp;gt;via RSS&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;
---
&amp;gt;   &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;prev_next.html&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;prev_url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;paginator.previous_page_path&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;prev_text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Previous page'&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;next_url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;paginator.next_page_path&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;next_text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Next page'&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;!--_--&gt;

&lt;p&gt;Basically, instead of iterating over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;site.posts&lt;/code&gt;, one has to loop over
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paginator.posts&lt;/code&gt;. In addition, pagination only works with the index.html file, which references the
home layout. I also had to rename the file index.md (default) to
index.html to fix this error:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Pagination: Pagination is enabled, but I couldn't find an index.html page to use as the pagination template. Skipping pagination.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;links-to-the-previous-and-next-pages&quot;&gt;Links to the previous and next pages&lt;/h2&gt;

&lt;p&gt;The links to the previous and next pages are implemented as a macro in the
&lt;a href=&quot;https://github.com/ouyi/ouyi.github.io/blob/master/_includes/prev_next.html&quot;&gt;prev_next.html
file&lt;/a&gt;.
The macro requires four named parameters: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prev_url&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prev_text&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;next_url&lt;/code&gt;,
and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;next_text&lt;/code&gt;. This line includes the macro and passes the required
parameters:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-liquid&quot; data-lang=&quot;liquid&quot;&gt;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;prev_next.html&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;prev_url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;paginator.previous_page_path&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;prev_text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Previous page'&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;next_url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;paginator.next_page_path&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;next_text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Next page'&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The same macro is reused by the &lt;a href=&quot;https://github.com/ouyi/ouyi.github.io/blob/master/_layouts/post.html&quot;&gt;post
layout&lt;/a&gt;
to render the links to the previous and next &lt;em&gt;posts&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As a side note, Jekyll pagination automatically generates the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tags
with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rel=&quot;next&quot;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rel=&quot;prev&quot;&lt;/code&gt; attributes as &lt;a href=&quot;https://webmasters.googleblog.com/2011/09/view-all-in-search-results.html&quot;&gt;recommended by
Google&lt;/a&gt;.
Those tags reside in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section of of the HTML page. They are not to
be mixed up with the hyperlinks generated by the above macro.&lt;/p&gt;

&lt;h2 id=&quot;categories-and-tags&quot;&gt;Categories and tags&lt;/h2&gt;

&lt;p&gt;Categories and tags are two largely overlapping concepts in Jekyll. From a
product perspective, I doubt the necessity of having both implemented, which do
not provide added value instead of confusing the users. The only difference
seems to be that categories become a part of the post URL. That means, a post
having the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;category: hadoop&lt;/code&gt; in the front matter would have a URL like
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://ouyi.github.io/hadoop/2017/10/08/hbase.html&lt;/code&gt;, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hadoop&lt;/code&gt; is the
category. That also means, posts published on the same date (or in the same
month or year) will be put into different folders, if they are of different
categories. I do not like that. One could also customize the permalink pattern
in Jekyll to remove the categories from the URL. But I chose to set the
categories of all posts to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post&lt;/code&gt;, e.g., the front matter of this post is:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
layout: post
title:  &quot;Customizing Jekyll theme&quot;
date:   2017-12-23 07:56:11 +0000
last_modified_at: 2017-12-31 14:05:16
category: post
tags: [Blogging, Jekyll]
---
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This way all posts go to the post category, which is simple and clear. The tags
are used to generate a &lt;a href=&quot;/tags/&quot;&gt;tags page&lt;/a&gt;, where all the posts are indexed by
tags. The page generation code can be found
&lt;a href=&quot;https://github.com/ouyi/ouyi.github.io/blob/master/tags.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;This post showed how to customize a theme in Jekyll, how to paginate the home
page, how to generate links to navigate to the neighbouring pages, and
discussed a bit about categories and tags. A separate post covers how to
&lt;a href=&quot;/post/2017/12/31/jekyll-table-of-contents.html&quot;&gt;automatically generate the table of contents in Jekyll&lt;/a&gt;.&lt;/p&gt;</content><author><name>Yi Ou</name></author><category term="post" /><category term="Blogging" /><category term="Jekyll" /><summary type="html">Jekyll supports a lot of themes, which work quite well out of the box. A theme is a pre-defined set of styles, templates, and template variables. My site is based on the default Jekyll theme: minima. I had to do some customizations to make it work as I wish. Luckily, Jekyll allows customizations of almost everything of the theme. This post shows some of the customizations this site applies on top of minima.</summary></entry><entry><title type="html">Sending emails from Java applications</title><link href="https://ouyi.github.io/post/2017/12/19/java-mail.html" rel="alternate" type="text/html" title="Sending emails from Java applications" /><published>2017-12-19T22:12:13+00:00</published><updated>2017-12-25T20:04:19+00:00</updated><id>https://ouyi.github.io/post/2017/12/19/java-mail</id><content type="html" xml:base="https://ouyi.github.io/post/2017/12/19/java-mail.html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#maven-dependencies&quot; id=&quot;markdown-toc-maven-dependencies&quot;&gt;Maven dependencies&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-mail-sending-service-implementation&quot; id=&quot;markdown-toc-the-mail-sending-service-implementation&quot;&gt;The mail sending service implementation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#unit-tests&quot; id=&quot;markdown-toc-unit-tests&quot;&gt;Unit tests&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#dependency-injection&quot; id=&quot;markdown-toc-dependency-injection&quot;&gt;Dependency injection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-rest-controller&quot; id=&quot;markdown-toc-the-rest-controller&quot;&gt;The REST controller&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#calling-the-rest-service-using-jquery-and-ajax&quot; id=&quot;markdown-toc-calling-the-rest-service-using-jquery-and-ajax&quot;&gt;Calling the REST service using JQuery and Ajax&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have so far worked with two ways of sending emails from Java applications.
The first way directly uses the JavaMail API, while the second one utilizes the
Spring framework, which makes it a bit easier to work with the JavaMail API.
The JavaMail Wikipedia page has sufficient &lt;a href=&quot;https://en.wikipedia.org/wiki/JavaMail&quot;&gt;examples for the direct use of the
JavaMail API&lt;/a&gt;. This post shows code
examples of the Spring one. We are implementing a Spring-based REST service
for sending emails.&lt;/p&gt;

&lt;h2 id=&quot;maven-dependencies&quot;&gt;Maven dependencies&lt;/h2&gt;

&lt;p&gt;The Spring support for emails is provided in the spring-context-support
artifact, and we also need the JavaMail artifact. Therefore, the following
dependencies are needed in the pom.xml file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-context-support&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${spring.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;javax.mail&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mail&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.4.7&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The dependency &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dumbster&lt;/code&gt; provides a fake SMTP server, to be used in the unit
tests:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.github.kirviq&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;dumbster&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.7.1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;test&lt;span class=&quot;nt&quot;&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;the-mail-sending-service-implementation&quot;&gt;The mail sending service implementation&lt;/h2&gt;

&lt;p&gt;We implement the following MailService interface, which can be used by, e.g., a Spring RestController
to expose an email-sending REST service.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.github.ouyi.mail.service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;javax.mail.MessagingException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MailService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sendMail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bodyText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attachmentPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MessagingException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The implementation looks like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.github.ouyi.mail.service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.beans.factory.annotation.Autowired&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.core.io.FileSystemResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.mail.javamail.JavaMailSender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.mail.javamail.MimeMessageHelper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;javax.mail.MessagingException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;javax.mail.internet.MimeMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Optional&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MailServiceImpl&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MailService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JavaMailSender&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mailSender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MailServiceImpl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;JavaMailSender&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mailSender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;mailSender&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mailSender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sendMail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bodyText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attachmentPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MessagingException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bodyText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attachmentPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ofNullable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attachmentPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;orElse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bodyText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attachmentPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;multipart&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MessagingException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;MimeMessage&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mailSender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createMimeMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;MimeMessageHelper&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;helper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MimeMessageHelper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;multipart&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setFrom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setSubject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bodyText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ofNullable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attachmentPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;orElse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;FileSystemResource&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FileSystemResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attachmentPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addAttachment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getFilename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;mailSender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note a multipart message is required in order to send an email with attachment.&lt;/p&gt;

&lt;h2 id=&quot;unit-tests&quot;&gt;Unit tests&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, we use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dumbster&lt;/code&gt; for unit testing:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.github.ouyi.mail.service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.dumbster.smtp.SimpleSmtpServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.dumbster.smtp.SmtpMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.junit.Test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.slf4j.Logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.slf4j.LoggerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.mail.javamail.JavaMailSenderImpl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.io.FileNotFoundException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.io.IOException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Properties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;junit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MailServiceImplTest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MailServiceImplTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;HEADER_KEY_TO&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;To&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;HEADER_KEY_SUBJECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Subject&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testSendMail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;smtpHost&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;localhost&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;smtpPort&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;12345&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;MY_SUBJECT&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;messageBody&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;MY_MESSAGE_BODY&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fromAddr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sender@localhost&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;toAddr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;test@test.com&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Start dumbster fake SMTP server...&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;SimpleSmtpServer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SimpleSmtpServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12345&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Send mail message&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;JavaMailSenderImpl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mailSender&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JavaMailSenderImpl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Properties&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mail.smtp.host&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;smtpHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mail.smtp.port&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;smtpPort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;mailSender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setJavaMailProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;MailServiceImpl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mailService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MailServiceImpl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mailSender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fromAddr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;mailService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sendMail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toAddr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;messageBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getReceivedEmails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;SmtpMessage&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getReceivedEmails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toAddr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getHeaderValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HEADER_KEY_TO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getHeaderValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HEADER_KEY_SUBJECT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;messageBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;dependency-injection&quot;&gt;Dependency injection&lt;/h2&gt;

&lt;p&gt;Since we use Spring, we can use dependency injection to create and configure a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JavaMailSenderImpl&lt;/code&gt; instance, which can be injected into the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MailServiceImpl&lt;/code&gt; instance:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;beans&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.springframework.org/schema/beans&quot;&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;xmlns:xsi=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;bean&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mailSender&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;org.springframework.mail.javamail.JavaMailSenderImpl&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;host&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my.smtp.host&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;port&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;587&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;username&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;pass&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

        &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;javaMailProperties&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;props&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;prop&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mail.smtp.auth&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;true&lt;span class=&quot;nt&quot;&gt;&amp;lt;/prop&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;prop&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mail.smtp.starttls.enable&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;true&lt;span class=&quot;nt&quot;&gt;&amp;lt;/prop&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/props&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/bean&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;bean&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mailService&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.hellokoding.account.service.MailServiceImpl&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;constructor-arg&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ref=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mailSender&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/constructor-arg&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;constructor-arg&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;java.lang.String&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sender@mydomain.com&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/constructor-arg&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/bean&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/beans&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;the-rest-controller&quot;&gt;The REST controller&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.github.ouyi.web&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.io.BufferedOutputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.io.File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.io.FileOutputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.io.OutputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;javax.servlet.http.HttpServletRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;javax.servlet.http.HttpServletResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;javax.servlet.http.HttpSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;javax.ws.rs.core.Response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.poi.xssf.usermodel.XSSFWorkbook&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.beans.factory.annotation.Autowired&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.stereotype.Controller&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.bind.annotation.RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.slf4j.Logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.slf4j.LoggerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.bind.annotation.ResponseBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Controller&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MailController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MailController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MailService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mailService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mailTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MAIL_SUBJECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;medshop order&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MAIL_CONTENT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Please find the order in the attachment.&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/sendmail&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;produces&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@ResponseBody&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Response&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sendmail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpServletRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpServletResponse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;File&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tempFile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;OutputStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outputStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;tempFile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createTempFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mail-attachment-&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;.xlsx&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;outputStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BufferedOutputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FileOutputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tempFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;XSSFWorkbook&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createSpreadsheet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;outputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;outputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;mailService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sendMail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mailTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MAIL_SUBJECT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MAIL_CONTENT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tempFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAbsolutePath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;outputStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;outputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tempFile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;tempFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sendmail&lt;/code&gt; method of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MailController&lt;/code&gt; has to return a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Response&lt;/code&gt; object. When I changed it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void&lt;/code&gt;, I got the following error response from my GlassFish server:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/1999/xhtml&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;&lt;/span&gt;GlassFish Server Open Source Edition  5.0  - Error report&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&amp;lt;style &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/css&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--H1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#525D76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;22px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;H2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#525D76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;16px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;H3&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#525D76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;14px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;BODY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;B&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#525D76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;P&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;12px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;HR&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#525D76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;&lt;/span&gt;HTTP Status 405 - Request method &lt;span class=&quot;ni&quot;&gt;&amp;amp;amp;&lt;/span&gt;#39&lt;span class=&quot;ni&quot;&gt;&amp;amp;#59;&lt;/span&gt;GET&lt;span class=&quot;ni&quot;&gt;&amp;amp;amp;&lt;/span&gt;#39&lt;span class=&quot;ni&quot;&gt;&amp;amp;#59;&lt;/span&gt; not supported&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&amp;lt;hr/&amp;gt;&amp;lt;p&amp;gt;&amp;lt;b&amp;gt;&lt;/span&gt;type&lt;span class=&quot;nt&quot;&gt;&amp;lt;/b&amp;gt;&lt;/span&gt; Status report&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;&amp;lt;b&amp;gt;&lt;/span&gt;message&lt;span class=&quot;nt&quot;&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;Request method &lt;span class=&quot;ni&quot;&gt;&amp;amp;amp;&lt;/span&gt;#39&lt;span class=&quot;ni&quot;&gt;&amp;amp;#59;&lt;/span&gt;GET&lt;span class=&quot;ni&quot;&gt;&amp;amp;amp;&lt;/span&gt;#39&lt;span class=&quot;ni&quot;&gt;&amp;amp;#59;&lt;/span&gt; not supported&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;&amp;lt;b&amp;gt;&lt;/span&gt;description&lt;span class=&quot;nt&quot;&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;The specified HTTP method is not allowed for the requested resource.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&amp;lt;hr/&amp;gt;&amp;lt;h3&amp;gt;&lt;/span&gt;GlassFish Server Open Source Edition  5.0 &lt;span class=&quot;nt&quot;&gt;&amp;lt;/h3&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When it returns a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Response&lt;/code&gt; object but without the annotation &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ResponseBody&lt;/code&gt;, I got another error response:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/1999/xhtml&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;&lt;/span&gt;GlassFish Server Open Source Edition  5.0  - Error report&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&amp;lt;style &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/css&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--H1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#525D76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;22px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;H2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#525D76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;16px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;H3&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#525D76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;14px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;BODY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;B&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#525D76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;P&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;12px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;HR&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#525D76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;&lt;/span&gt;HTTP Status 404 - Not Found&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&amp;lt;hr/&amp;gt;&amp;lt;p&amp;gt;&amp;lt;b&amp;gt;&lt;/span&gt;type&lt;span class=&quot;nt&quot;&gt;&amp;lt;/b&amp;gt;&lt;/span&gt; Status report&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;&amp;lt;b&amp;gt;&lt;/span&gt;message&lt;span class=&quot;nt&quot;&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;Not Found&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;&amp;lt;b&amp;gt;&lt;/span&gt;description&lt;span class=&quot;nt&quot;&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;The requested resource is not available.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&amp;lt;hr/&amp;gt;&amp;lt;h3&amp;gt;&lt;/span&gt;GlassFish Server Open Source Edition  5.0 &lt;span class=&quot;nt&quot;&gt;&amp;lt;/h3&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;calling-the-rest-service-using-jquery-and-ajax&quot;&gt;Calling the REST service using JQuery and Ajax&lt;/h2&gt;

&lt;p&gt;Our mail-sending REST service can be consumed by the frontend JavaScript code as follows:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ready&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ajaxPrefilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;originalOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;jqXHR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;jqXHR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setRequestHeader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;${_csrf.headerName}&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;${_csrf.token}&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#sendmail-button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ajax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;${contextPath}/sendmail&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;dataType&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Email sent successfully!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Failed to send email!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;${_csrf.headerName}&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;${_csrf.token}&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;${contextPath}&lt;/code&gt; are
templated on the server-side using jsp. If server-side templating is not used,
the CSRF header name and token can be passed to the client as meta tags in the
HTML head section, which can then be retrieved by JavaScript code. Details can be found in the &lt;a href=&quot;https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-include-csrf-token-ajax&quot;&gt;Spring security
documentation&lt;/a&gt;.
The context path, which is quite static, can be easily injected into the frontend code at build time.&lt;/p&gt;</content><author><name>Yi Ou</name></author><category term="post" /><category term="Java" /><category term="Spring" /><summary type="html">I have so far worked with two ways of sending emails from Java applications. The first way directly uses the JavaMail API, while the second one utilizes the Spring framework, which makes it a bit easier to work with the JavaMail API. The JavaMail Wikipedia page has sufficient examples for the direct use of the JavaMail API. This post shows code examples of the Spring one. We are implementing a Spring-based REST service for sending emails.</summary></entry><entry><title type="html">Understanding Gradle DSL</title><link href="https://ouyi.github.io/post/2017/12/09/groovy-gradle.html" rel="alternate" type="text/html" title="Understanding Gradle DSL" /><published>2017-12-09T19:25:13+00:00</published><updated>2017-12-27T16:45:09+00:00</updated><id>https://ouyi.github.io/post/2017/12/09/groovy-gradle</id><content type="html" xml:base="https://ouyi.github.io/post/2017/12/09/groovy-gradle.html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#groovy-basics&quot; id=&quot;markdown-toc-groovy-basics&quot;&gt;Groovy basics&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#closure&quot; id=&quot;markdown-toc-closure&quot;&gt;Closure&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#method-calls&quot; id=&quot;markdown-toc-method-calls&quot;&gt;Method calls&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#gradle-basics&quot; id=&quot;markdown-toc-gradle-basics&quot;&gt;Gradle basics&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#dependencies&quot; id=&quot;markdown-toc-dependencies&quot;&gt;Dependencies&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#tasks&quot; id=&quot;markdown-toc-tasks&quot;&gt;Tasks&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#custom-tasks&quot; id=&quot;markdown-toc-custom-tasks&quot;&gt;Custom tasks&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of the time, Gradle works just fine for me, but I had difficulties
understanding the build script and the Gradle DSL documentation. This post
documents the points which helped me to understand them.&lt;/p&gt;

&lt;h2 id=&quot;groovy-basics&quot;&gt;Groovy basics&lt;/h2&gt;
&lt;p&gt;To understand Gradle, we first need to understand a bit of Groovy, because Gradle is based on Groovy.&lt;/p&gt;

&lt;h3 id=&quot;closure&quot;&gt;Closure&lt;/h3&gt;

&lt;p&gt;A &lt;em&gt;closure&lt;/em&gt; is a function bound to (or executed against) some object instances,
which can be one of these three things: &lt;em&gt;this&lt;/em&gt;, &lt;em&gt;owner&lt;/em&gt;, and &lt;em&gt;delegate&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;this: the instance of the enclosing class&lt;/li&gt;
  &lt;li&gt;owner: same as this, or the enclosing closure if exists&lt;/li&gt;
  &lt;li&gt;delegate: same as owner, but can be changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default, a variable accessed in a Groovy closure is resolved as follows:
the closure itself will be checked first, followed by the closure’s this scope,
then the closure’s owner, then its delegate.&lt;/p&gt;

&lt;p&gt;The following example demostrates changing the delegate of a closure:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyOtherClass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;myString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;I am in MyOtherClass&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyClass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;myClosure&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;myString&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// myString is undefined (neither on this nor on owner)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;MyClass&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;myClass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MyClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;closure&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MyClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;myClosure&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;closure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MyOtherClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Changing the delegate makes variables in the new delegate available to the closure&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;closure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;// outputs: &quot;I am in MyOtherClass&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It is important to notice that &lt;em&gt;closure can access member fields and methods defined in another object, which can be changed dynamically at runtime&lt;/em&gt;.
As a side note: closure and lambda are different concepts. A lambda is simply an anonymous function, without all the this, owner, and delegate stuff.&lt;/p&gt;

&lt;h3 id=&quot;method-calls&quot;&gt;Method calls&lt;/h3&gt;

&lt;p&gt;Method calls in Groovy look quite different than in Java. The following are all method call examples in Groovy:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// System.out is imported by default, no parenthesis needed if there is at least one parameter&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// prints the sequence 1, 2, and 3&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;printArgs&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;a:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;b:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// named parameters are collected and passed as a map, if the first parameter of the method is a map&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;doStuff&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// closure as the method parameter, `it` is the default closure parameter&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// for each of the list items, execute the closure, with the item as parameter&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// same as above, the closure parameter can also be specified explicitly&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The return type distinguishes method definitions from method calls, e.g., this is a method definition:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;repositories&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;in a closure&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;gradle-basics&quot;&gt;Gradle basics&lt;/h2&gt;

&lt;p&gt;With a basic understanding of closures and method calls in Groovy, we can try to make sense of the following method calls:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;repositories&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;in a closure&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// call the repositories() method defined on the project object&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;repositories&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;in a closure&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// same as above, no parenthesis needed&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note in Gradle DSL, closures are frequently (and idiomatically) used as the
last method parameter to configure some object. This is a pattern called
&lt;a href=&quot;https://docs.gradle.org/current/userguide/writing_build_scripts.html#groovy-dsl-basics&quot;&gt;configuration closure&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Each closure has a delegate object, which Groovy uses to look up variable and
method references which are not local variables or parameters of the closure.
Gradle uses this for configuration closures, where the delegate object is set
to the object to be configured.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Gradle processes a build script in three phases: &lt;em&gt;initialization&lt;/em&gt;, &lt;em&gt;configuration&lt;/em&gt;,
and &lt;em&gt;execution&lt;/em&gt;. The initialization and configuration phases create and configure
project objects and construct a DAG of tasks, which are then executed in the
execution phase.&lt;/p&gt;

&lt;h3 id=&quot;dependencies&quot;&gt;Dependencies&lt;/h3&gt;

&lt;p&gt;With the above being discussed, I understand the following is calling the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dependencies()&lt;/code&gt; method on the project object.
And the documentation indicates the configuration closure is executed against the DependencyHandler object.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;dependencies&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;testCompile&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;group:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'junit'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'junit'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;version:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'4.12'&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;But where is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testCompile&lt;/code&gt; defined?&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/dsl/DependencyHandler.html&quot;&gt;DependencyHandler documentation&lt;/a&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;To declare a specific dependency for a configuration you can use the following syntax:&lt;/p&gt;

  &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dependencies {
    configurationName dependencyNotation1, dependencyNotation2, ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;So &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testCompile&lt;/code&gt; is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;configurationName&lt;/code&gt;, but where is this configurationName defined? It turns out that Gradle plugins can add various stuff to the project.
In our case, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testCompile&lt;/code&gt; is a &lt;a href=&quot;https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_plugin_and_dependency_management&quot;&gt;dependency configuration added by the java plugin&lt;/a&gt;. That is why we need this line in our build script:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;plugin:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'java'&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//so that we can use 'compile', 'testCompile' for dependencies&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;tasks&quot;&gt;Tasks&lt;/h3&gt;

&lt;p&gt;Gradle plugins can also add tasks to the project, e.g., the &lt;a href=&quot;https://docs.gradle.org/current/userguide/java_plugin.html#sec:jar&quot;&gt;jar task added by the java plugin&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;jar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;manifest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;s1&quot;&gt;'Class-Path'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configurations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;compile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;collect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;' '&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;s1&quot;&gt;'Main-Class'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'hello.HelloWorld'&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The documentation says:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Each jar or war object has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manifest&lt;/code&gt; property with a separate instance of Manifest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This explains the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manifest {}&lt;/code&gt; construct. Following the &lt;a href=&quot;https://docs.gradle.org/current/javadoc/org/gradle/api/java/archives/Manifest.html&quot;&gt;documentation of
Manifest&lt;/a&gt;,
we can find the method signature &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Manifest attributes(Map&amp;lt;String,?&amp;gt;
attributes)&lt;/code&gt;, which explains the method call with named parameters
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attributes(key1:value1, key2:value2)&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;custom-tasks&quot;&gt;Custom tasks&lt;/h3&gt;

&lt;p&gt;The syntax of a custom task is tricky. For example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;myTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;type:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dependsOn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anotherTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// clousre&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I could figure out that this was probably calling the method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task
task(Map&amp;lt;String,?&amp;gt; args, String name, Closure configureClosure)&lt;/code&gt; &lt;a href=&quot;https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html#task(java.util.Map,%20java.lang.String,%20groovy.lang.Closure)&quot;&gt;defined on
the project object&lt;/a&gt;,
but I had no clue how to match the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myTask()&lt;/code&gt; construct with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;
parameter. And I am not alone, similar disussions are
&lt;a href=&quot;https://discuss.gradle.org/t/how-to-translate-task-keyword-in-dsl-into-groovy-call/7243&quot;&gt;here&lt;/a&gt;
and
&lt;a href=&quot;https://stackoverflow.com/questions/27584463/understanding-the-groovy-syntax-in-a-gradle-task-definition&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It turns out that Gradle uses some advanced meta programming features of Groovy
(compile-time metaprogramming) to transform the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myTask()&lt;/code&gt; construct to the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; parameter. To be honest, this is the part of Gradle that I do not like,
because it seems to be too tricky to implement those syntax sugars (and too
much sugar may not be healthy). Afterall, Gradle is just a build tool, which
shall be easy to understand, to use, and to extend.&lt;/p&gt;

&lt;p&gt;Overall, I find Gradle really cool, because build scripts written in Gradle DSL
(or in Groovy) seem to be much cleaner than in XML. With Maven (or Ant), one
has to read or write build logic in XML, which is supposed to be processed by
machines :wink:.&lt;/p&gt;</content><author><name>Yi Ou</name></author><category term="post" /><category term="Groovy" /><category term="Gradle" /><category term="CI/CD" /><summary type="html">Most of the time, Gradle works just fine for me, but I had difficulties understanding the build script and the Gradle DSL documentation. This post documents the points which helped me to understand them.</summary></entry><entry><title type="html">Migrating from Blogspot to GitHub Pages</title><link href="https://ouyi.github.io/post/2017/10/14/blog-migration.html" rel="alternate" type="text/html" title="Migrating from Blogspot to GitHub Pages" /><published>2017-10-14T07:56:11+00:00</published><updated>2018-01-14T19:55:39+00:00</updated><id>https://ouyi.github.io/post/2017/10/14/blog-migration</id><content type="html" xml:base="https://ouyi.github.io/post/2017/10/14/blog-migration.html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#travis-ci&quot; id=&quot;markdown-toc-travis-ci&quot;&gt;Travis CI&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#hosting-images&quot; id=&quot;markdown-toc-hosting-images&quot;&gt;Hosting images&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#redirects-and-canonical-url-tags&quot; id=&quot;markdown-toc-redirects-and-canonical-url-tags&quot;&gt;Redirects and canonical URL tags&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Recently I migrated &lt;a href=&quot;https://ouyi.github.io&quot;&gt;my blog&lt;/a&gt; from Blogspot to &lt;a href=&quot;https://github.com/ouyi/ouyi.github.io&quot;&gt;GitHub
Pages&lt;/a&gt;. It took a while, but I am glad
I did it, because blogging with GitHub Pages is much more enjoyable than with
Blogspot, as long as one is comfortable with Git and Markdown. More
specifically, I like GitHub Pages because:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Everything of the blog is version controlled, including posts, themes, and settings&lt;/li&gt;
  &lt;li&gt;Static site generators (I chose Jekyll) allow customizations of almost everything&lt;/li&gt;
  &lt;li&gt;Markdown produces much cleaner HTML pages than those produced by a WYSIWYG editor&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With Blogspot I already had some of my posts in Markdown and in a Git
repository. But to publish a post there, I had to convert the post from
Markdown to HTML with some tools, and then paste the result page into the
Blogspot post editor Web UI.&lt;/p&gt;

&lt;p&gt;Now with GitHub Pages and Jekyll, the workflow of blogging is like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Writing a post in Markdown with any text editor, and&lt;/li&gt;
  &lt;li&gt;Add, commit, and push the changes in Git&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;GitHub Pages will automatically run a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll build&lt;/code&gt; to generate the HTML pages,
which usually become online in a couple of seconds after the push.&lt;/p&gt;

&lt;p&gt;The migration process, however, was not always straightforward. I took notes so
that it might be helpful for the readers.&lt;/p&gt;

&lt;h2 id=&quot;travis-ci&quot;&gt;Travis CI&lt;/h2&gt;

&lt;p&gt;The downside of having GitHub Pages building everything automatically behind
the scenes is that if there is an issue, e.g., a Markdown syntax error, you
would not even notice it. Therefore, I configured it to be built also
automatically on Travis CI, which requires just a config file (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.travis.yml&lt;/code&gt;)
&lt;a href=&quot;https://github.com/ouyi/ouyi.github.io/blob/master/.travis.yml&quot;&gt;in the root folder of the
project&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;language&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundler&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle install&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec jekyll build --safe&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec htmlproofer ./_site --disable-external&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;!--_--&gt;

&lt;p&gt;This instructs Travis to generate the site (stored in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt; folder) and
run htmlproofer on it while ignoring the linked external sites. For all this to
work, of course, one has to connect the GitHub project in Travis CI, which is
out of scope of this post (and GitHub Pages also works without Travis CI).&lt;/p&gt;

&lt;p&gt;When all this has been set up, one got email notifications from Travis
on build failures. One got also a small build passing (or failing) badge:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://travis-ci.org/ouyi/ouyi.github.io&quot;&gt;&lt;img src=&quot;https://travis-ci.org/ouyi/ouyi.github.io.svg?branch=master&quot; alt=&quot;Build Status&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The command htmlproofer is provided by the gem &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;html-proofer&lt;/code&gt;. In addition, to
make sure the build on Travis has the same dependencies as the build on GitHub,
specify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gem 'github-pages', group: :jekyll_plugins&lt;/code&gt; in the
&lt;a href=&quot;https://github.com/ouyi/ouyi.github.io/blob/master/Gemfile&quot;&gt;Gemfile&lt;/a&gt;. Before
the changes are pushed, one can always run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle exec jekyll serve --port
8080 --host 0.0.0.0 --drafts&lt;/code&gt; and open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:8080&lt;/code&gt; with the browser
to proof read the generated site.&lt;/p&gt;

&lt;h2 id=&quot;hosting-images&quot;&gt;Hosting images&lt;/h2&gt;

&lt;p&gt;I mentioned earlier: “everything of the blog is version controlled”. This does
NOT apply to images, because I do not think including images in a Git
repository is a great idea. But we need to host the images somewhere, e.g., S3,
CDN, or any other hosting services. The solution I chose was found on Statck
Overflow. It allows me to host my images on GitHub, based on a &lt;a href=&quot;https://stackoverflow.com/a/20959426/8886552&quot;&gt;secret GitHub
feature&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once I got the URL to the image, I can add the image using standard Markdown
syntax (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;![alt text](image_url &quot;title text&quot;)&lt;/code&gt;) or the HTML &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;img&lt;/code&gt; tag
(yes, in a Markdown file), e.g.:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;img&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://user-images.githubusercontent.com/15970333/32409768-84c92cc8-c1b2-11e7-9309-428c99da8cac.png&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;alt=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;screen shot 1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;redirects-and-canonical-url-tags&quot;&gt;Redirects and canonical URL tags&lt;/h2&gt;

&lt;p&gt;After a post has been migrated, there are two versions of it on the Web: the
old one on Blogspot and the new one on GitHub. Simply removing the old version
would mean a bad user experience (they might have bookmarked the old version)
and would hurt SEO. Ideally, we would like to:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;redirect the browser visiting the old version to the new version, and&lt;/li&gt;
  &lt;li&gt;tell search engines that the old version is obsolete and the new version is
the one to index.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After some searches, I came up with this kind of Blogspot template code
snippet:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;b:if&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cond=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'data:blog.url == &amp;amp;quot;https://ouyi-cs.blogspot.com/2015/12/hadoop-streaming-broken-pipe-issue.html&amp;amp;quot;'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'https://ouyi.github.io/hadoop/2015/12/06/hadoop-streaming.html'&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'canonical'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'0; url=https://ouyi.github.io/hadoop/2015/12/06/hadoop-streaming.html'&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;http-equiv=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'refresh'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;b:elseif&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cond=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'data:blog.url == &amp;amp;quot;http://ouyi-cs.blogspot.com/2015/12/hadoop-streaming-broken-pipe-issue.html&amp;amp;quot;'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'https://ouyi.github.io/hadoop/2015/12/06/hadoop-streaming.html'&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'canonical'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'0; url=https://ouyi.github.io/hadoop/2015/12/06/hadoop-streaming.html'&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;http-equiv=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'refresh'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;b:elseif&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cond=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'data:blog.url == &amp;amp;quot;https://ouyi-cs.blogspot.com/2015/12/downgrade-rpms-using-ansible.html&amp;amp;quot;'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'https://ouyi.github.io/cicd/2015/12/14/ansible-downgrade-rpm.html'&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'canonical'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'0; url=https://ouyi.github.io/cicd/2015/12/14/ansible-downgrade-rpm.html'&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;http-equiv=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'refresh'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;b:elseif&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cond=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'data:blog.url == &amp;amp;quot;http://ouyi-cs.blogspot.com/2015/12/downgrade-rpms-using-ansible.html&amp;amp;quot;'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'https://ouyi.github.io/cicd/2015/12/14/ansible-downgrade-rpm.html'&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'canonical'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'0; url=https://ouyi.github.io/cicd/2015/12/14/ansible-downgrade-rpm.html'&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;http-equiv=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'refresh'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- one such block for each post migrated ... --&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;b:else/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/b:if&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The code snippet can be added to the Blogspot HTML template (in blogger.com Web
UI, click “Theme”, then “Edit HTML”), directly after the opening &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag.
For each migrated post, it will generate two tags (link and meta) in the head
section of the Blogspot version. The meta tag with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http-equiv='refresh'&lt;/code&gt; takes
care of redirecting the browser to the GitHub version. The link tag tells
search engines that the GitHub version is the one (and the only one) to be
indexed. Note also that I had to take care of both the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https&lt;/code&gt; URLs
for the Blogspot version.&lt;/p&gt;

&lt;p&gt;The syntax seems to be overwhelmingly verbose (working with Jekyll and Liquid,
its template system, is way easier). If you find a cleaner or shorter way of
doing this, please let me know. More details about the Blogspot template syntax
&lt;a href=&quot;https://support.google.com/blogger/answer/46995?hl=en&amp;amp;ref_topic=6321969&quot;&gt;can be found
here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After a few weeks, search engines will remove the Blogspot version from their
indexed pages and index the GitHub one. The indexing process is quite out of
our control. One has to be patient. From what I observed, Google seems to have
picked up the posts on the new site faster than Bing and Yahoo. The following
are a couple of articles related to “page URL changes” I found useful:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.google.com/webmasters/answer/6033049&quot;&gt;Move a site with URL changes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://webmasters.googleblog.com/2009/12/handling-legitimate-cross-domain.html&quot;&gt;Handling legitimate cross-domain content duplication&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://webapps.stackexchange.com/questions/6140/301-redirect-for-specific-post-in-blogger-blog&quot;&gt;301 redirect for specific post in Blogger blog?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The blog migration also involves customization of the Jekyll theme I chose, which
is described in &lt;a href=&quot;/post/2017/12/23/jekyll-customization.html&quot;&gt;a separate post&lt;/a&gt;.&lt;/p&gt;</content><author><name>Yi Ou</name></author><category term="post" /><category term="Blogging" /><category term="Jekyll" /><summary type="html">Recently I migrated my blog from Blogspot to GitHub Pages. It took a while, but I am glad I did it, because blogging with GitHub Pages is much more enjoyable than with Blogspot, as long as one is comfortable with Git and Markdown. More specifically, I like GitHub Pages because:</summary></entry><entry xml:lang="zh"><title type="html">理解HBase和BigTable</title><link href="https://ouyi.github.io/hadoop/2017/10/08/hbase.html" rel="alternate" type="text/html" title="理解HBase和BigTable" /><published>2017-10-08T22:42:27+00:00</published><updated>2018-10-12T20:10:03+00:00</updated><id>https://ouyi.github.io/hadoop/2017/10/08/hbase</id><content type="html" xml:base="https://ouyi.github.io/hadoop/2017/10/08/hbase.html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#都在术语里边&quot; id=&quot;markdown-toc-都在术语里边&quot;&gt;都在术语里边&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#映射表&quot; id=&quot;markdown-toc-映射表&quot;&gt;映射表&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#持久化&quot; id=&quot;markdown-toc-持久化&quot;&gt;持久化&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#分布式&quot; id=&quot;markdown-toc-分布式&quot;&gt;分布式&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#有序的&quot; id=&quot;markdown-toc-有序的&quot;&gt;有序的&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#多维度&quot; id=&quot;markdown-toc-多维度&quot;&gt;多维度&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#稀疏&quot; id=&quot;markdown-toc-稀疏&quot;&gt;稀疏&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#结束语&quot; id=&quot;markdown-toc-结束语&quot;&gt;结束语&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文翻译自Jim R. Wilson的&lt;a href=&quot;https://dzone.com/articles/understanding-hbase-and-bigtab&quot;&gt;Understanding HBase and BigTable&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;学习HBase（Google BigTable的开源实现）最难的部分，就是从概念上理解它到底是什么。&lt;/p&gt;

&lt;p&gt;我觉得挺不幸的是，这两个很棒的系统的名字用了表（table) 和库（base）这样的字眼，这很容易把熟悉关系型数据库的那些人（像我自己）搞晕。&lt;/p&gt;

&lt;p&gt;这篇文章主要想从概念上描述清楚这两个分布式数据存储系统，读完它以后，当你面对“该用HBase还是该用传统数据库”这样的问题的时候，应该能够做出更加明智的决定。&lt;/p&gt;

&lt;h2 id=&quot;都在术语里边&quot;&gt;都在术语里边&lt;/h2&gt;

&lt;p&gt;幸运的是，&lt;a href=&quot;https://research.google.com/archive/bigtable.html&quot;&gt;Google的BigTable论文&lt;/a&gt;清楚地解释了BigTable实际上是什么。这是论文“Data Model”部分的第一句话：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;BigTable是一个稀疏的，分布式的，持久化的，多维的，有序的映射表(map)。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;注意：读完上面这句话以后，有些读者可能有点晕，现在可以定一下神。&lt;/p&gt;

&lt;p&gt;这篇论文接着解释道：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;这个map是用row key，column key, 和timestamp来做索引的，每个在map里的值都是未经处理的字节数组。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;同样地，&lt;a href=&quot;http://HBase.apache.org/book.html#_architecture&quot;&gt;HBase架构文档&lt;/a&gt;指出：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;HBase使用的数据模型与BigTable非常相似。用户将数据行存在有标签的表中。一个数据行有一个可排序的key 和任意多个列。表的存储是稀疏的，所以只要用户喜欢，同一张表的数据行能够有疯狂变化着的不同数量的列。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;虽然这一切看起来相当神秘，但是当你把它分成一个个单词的话，就可以理解了。我将按照以下这个顺序来讨论它们：映射表，持久化，分布式，有序，多维度，和稀疏。&lt;/p&gt;

&lt;p&gt;与其尝试整个儿来理解一个系统，我发现化整为零更容易理解。&lt;/p&gt;

&lt;h2 id=&quot;映射表&quot;&gt;映射表&lt;/h2&gt;

&lt;p&gt;本质上来说，HBase和BigTable就是一个map。取决于编程语言背景，你可能更熟悉关联数组（PHP），字典（Python），哈希（Ruby），或者对象（JavaScript）。&lt;/p&gt;

&lt;p&gt;根据维基百科，map是一个抽象的数据类型，由一个键的集合和一个值的集合组成，每一个键对应一个值。&lt;/p&gt;

&lt;p&gt;下边的是个简单的用JSON语法描述的map的例子，它的所有值都是字符串类型：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;zzzzz&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;woot&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;xyz&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;aaaab&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;aaaaa&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;持久化&quot;&gt;持久化&lt;/h2&gt;

&lt;p&gt;持久化就是说你存进这个特殊的map中的数据“一直存在”，即使创建了或访问了它的程序都已经者结束了。这个概念与其他的持久化存储没什么区别，比如文件系统。接着讲。。。&lt;/p&gt;

&lt;h2 id=&quot;分布式&quot;&gt;分布式&lt;/h2&gt;

&lt;p&gt;HBase和BigTable建立于分布式文件系统之上，以致于底层的文件存储能够分布在一系列的独立的机器上。&lt;/p&gt;

&lt;p&gt;HBase是基于&lt;a href=&quot;https://en.wikipedia.org/wiki/Apache_Hadoop#HDFS&quot;&gt;Hadoop的分布式文件系统（HDFS）&lt;/a&gt;或者&lt;a href=&quot;https://en.wikipedia.org/wiki/Amazon_S3&quot;&gt;亚马逊的简单存储服务（S3）&lt;/a&gt;的上层应用，而BigTable则是基于&lt;a href=&quot;https://research.google.com/archive/gfs.html&quot;&gt;Google文件系统（GFS）&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;数据的副本被&lt;a href=&quot;https://en.wikipedia.org/wiki/Replication_(computing)&quot;&gt;复制&lt;/a&gt;到多个机器节点上的方式和数据在&lt;a href=&quot;http://en.wikipedia.org/wiki/RAID&quot;&gt;磁盘冗余阵列（RAID）&lt;/a&gt;里面分布的方式有点相似。&lt;/p&gt;

&lt;p&gt;对于本文的目的来说，我们并不关心具体使用的是哪种分布式文件系统。重要的是知道它是分布的，这样数据在比如集群内的某个节点挂掉时，有一定保障。&lt;/p&gt;

&lt;h2 id=&quot;有序的&quot;&gt;有序的&lt;/h2&gt;

&lt;p&gt;不同于大部分的map的实现，在HBase/BigTable里，键值对严格按字母表顺序排序，就是说键为”aaaaa”的数据行的下一个行键应该是”aaaab”，并且列键为”zzzzz”的数据行比较远。&lt;/p&gt;

&lt;p&gt;继续我们的JSON例子，排序后是这样：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;aaaaa&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;aaaab&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;xyz&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;zzzzz&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;woot&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;由于这些系统通常很大很分布，这个有序的特点实际上非常重要。相近的键对应的数据行存储在一起，这样当你需要扫描表的时候，你最感兴趣的记录是彼此临近的。&lt;/p&gt;

&lt;p&gt;这一点对于选择键的规范是重要的。举个例子，如果一个表的键是域名，将域名反过来写是非常有必要的（用“com.jinbojw.www”而不是“www.jimbojw.com”)，这样子域名的数据行会比较临近父域名的数据行。&lt;/p&gt;

&lt;p&gt;继续考虑域名这个例子，键为“mail.jimbojw.com”的数据行将会靠近“com.jimbojw.www”的数据行，而不是临近比如“mail.xyz.com”这种数据行。如果不将域名反过来，就会出现后者这种情况。&lt;/p&gt;

&lt;p&gt;注意HBase/BigTable中的有序不是值有序而是键有序。除了键，没有自动索引任何其它东西，这一点就好象普通的map实现一样。&lt;/p&gt;

&lt;h2 id=&quot;多维度&quot;&gt;多维度&lt;/h2&gt;

&lt;p&gt;直到目前，我们还没有提到任何“列”的概念，在概念上我们把“table”当成普通的hash/map，这完全是有意的。“列”这个词像“表”和“库”一样是另一个沉重的单词，承担了多年的关系型数据库经验的感情包袱。&lt;/p&gt;

&lt;p&gt;相反，我觉得将它看做一个多维度的map（或者嵌套的map，如果愿意的话）更容易。我们的JSON例子添加一个维度的话，就成了下边这个样子：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;A&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;B&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;z&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;aaaaa&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;A&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;B&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;aaaab&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;A&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;B&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ocean&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;xyz&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;A&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;B&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;there&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;zzzzz&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;A&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;woot&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;B&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1337&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;在上边的例子中，你会注意到，现在每个键都指向一个正好有两个键“A”“B”的map。从现在开始，我们把最顶层的键值对看做是“行”。而且，按照BigTable和HBase的命名规则，“A”和“B”映射表则被称为“列簇”。&lt;/p&gt;

&lt;p&gt;一个表的列簇在表被创建的时候就被指定了。并且以后难以或者说不可能被修改。添加一个列簇也有可能非常昂贵，所以最好一开始就一次性指定好你需要的列簇。&lt;/p&gt;

&lt;p&gt;幸运的是，一个列簇可能有任意个数量的列，用“column qualifier”或者“列标签”来标识。下边我们的JSON例子的一部分，这次加上了列标识：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;aaaaa&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;aaaab&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;domination&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ocean&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;注意这两行数据里，“A”列簇有两个列：“foo”和“bar”，“B”列簇仅有一列，并且其列标识为空字符串。&lt;/p&gt;

&lt;p&gt;当查询HBase/BigTable的数据时，你必须提供全列名，格式为“&lt;family&gt;:&lt;qualifier&gt;”，举个例子，上边的例子中共有三列：“A:foo”，“A:bar”，和“B:”。&lt;/qualifier&gt;&lt;/family&gt;&lt;/p&gt;

&lt;p&gt;注意，虽然列簇是不变的，但是列是可变的。考虑下面这行扩展了的数据：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;zzzzz&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;catch_phrase&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;woot&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;这里，数据行“zzzzz”有且仅有一列，“A:catch_phrase”。因为每行可有任意多的不同的列，没有内置的方法来列出所有行中的所有列。要得到这个信息，你得做一个全表扫描。但是你可以查询所有的列簇，因为他们（基本上）是不可变的。&lt;/p&gt;

&lt;p&gt;HBase/BigTable里最后的维度是时间。所有的数据都是有版本的，要么是整数的时间戳（Unix时间戳），或者用户选择的其他整数。客户端会在插入数据的时候指定时间戳。&lt;/p&gt;

&lt;p&gt;考虑加上了用户指定的整数时间戳的例子：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;aaaaa&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;每个列簇可以自己决定一个数据单元保留多少个版本（一个rowkey/column唯一指定一个数据单元）。大多数的情况下，应用只会简单的请求给定的数据单元的数据，而不指定时间戳。那样，HBase/BigTable将会返回最新的版本（时间戳最大的版本），因为它是按时间倒序排序的。&lt;/p&gt;

&lt;p&gt;如果应用程序要获取一个特定的行并指定时间戳，HBase将会返回时间戳小于或等于给定的时间戳的那个数据单元。&lt;/p&gt;

&lt;p&gt;用我们假想的HBase表，查询行／列为“aaaaa”/“A:foo”的数据单元将会返回”y“，而查询行／列／时间戳 为“aaaaa”/“A:foo”/10的数据单元将会返回“m”，当查询行／列／时间戳 为“aaaaa”/“A:foo”/2 将会返回一个空的结果。&lt;/p&gt;

&lt;h2 id=&quot;稀疏&quot;&gt;稀疏&lt;/h2&gt;

&lt;p&gt;最后的关键词是稀疏。像上边提到的，一个给定的数据行的每个列簇能有任意多的列，或者根本没有列。另一种类型的稀疏是基于数据行的空隙，仅指键值之间的空隙。&lt;/p&gt;

&lt;p&gt;这只有用本文基于map的术语，而不是用关系型数据库的相关概念来理解HBase/BigTable，才会觉得非常合理。&lt;/p&gt;

&lt;h2 id=&quot;结束语&quot;&gt;结束语&lt;/h2&gt;

&lt;p&gt;希望对你理解HBase的数据模型有所帮助。&lt;/p&gt;</content><author><name>Yi Ou</name></author><category term="hadoop" /><category term="Big Data" /><category term="Translation" /><summary type="html">本文翻译自Jim R. Wilson的Understanding HBase and BigTable。</summary></entry></feed>